From 42f64cf2bf37434ccf905e50e8553de6bf6ca5d0 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 09:52:48 -0500 Subject: [PATCH 001/247] Remove old Schedule module --- .../src/schedule/ambiguity_detection.rs | 612 ------- crates/bevy_ecs/src/schedule/executor.rs | 36 - .../src/schedule/executor_parallel.rs | 564 ------ crates/bevy_ecs/src/schedule/graph_utils.rs | 126 -- crates/bevy_ecs/src/schedule/label.rs | 21 - crates/bevy_ecs/src/schedule/mod.rs | 408 ----- crates/bevy_ecs/src/schedule/run_criteria.rs | 402 ---- crates/bevy_ecs/src/schedule/stage.rs | 1614 ----------------- crates/bevy_ecs/src/schedule/state.rs | 757 -------- .../bevy_ecs/src/schedule/system_container.rs | 111 -- .../src/schedule/system_descriptor.rs | 274 --- crates/bevy_ecs/src/schedule/system_set.rs | 116 -- 12 files changed, 5041 deletions(-) delete mode 100644 crates/bevy_ecs/src/schedule/ambiguity_detection.rs delete mode 100644 crates/bevy_ecs/src/schedule/executor.rs delete mode 100644 crates/bevy_ecs/src/schedule/executor_parallel.rs delete mode 100644 crates/bevy_ecs/src/schedule/graph_utils.rs delete mode 100644 crates/bevy_ecs/src/schedule/label.rs delete mode 100644 crates/bevy_ecs/src/schedule/mod.rs delete mode 100644 crates/bevy_ecs/src/schedule/run_criteria.rs delete mode 100644 crates/bevy_ecs/src/schedule/stage.rs delete mode 100644 crates/bevy_ecs/src/schedule/state.rs delete mode 100644 crates/bevy_ecs/src/schedule/system_container.rs delete mode 100644 crates/bevy_ecs/src/schedule/system_descriptor.rs delete mode 100644 crates/bevy_ecs/src/schedule/system_set.rs diff --git a/crates/bevy_ecs/src/schedule/ambiguity_detection.rs b/crates/bevy_ecs/src/schedule/ambiguity_detection.rs deleted file mode 100644 index 5e1269309154d..0000000000000 --- a/crates/bevy_ecs/src/schedule/ambiguity_detection.rs +++ /dev/null @@ -1,612 +0,0 @@ -use bevy_utils::tracing::info; -use fixedbitset::FixedBitSet; - -use crate::component::ComponentId; -use crate::schedule::{AmbiguityDetection, GraphNode, SystemContainer, SystemStage}; -use crate::world::World; - -use super::SystemLabelId; - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct SystemOrderAmbiguity { - segment: SystemStageSegment, - // Note: In order for comparisons to work correctly, - // `system_names` and `conflicts` must be sorted at all times. - system_names: [String; 2], - conflicts: Vec, -} - -/// Which part of a [`SystemStage`] was a [`SystemOrderAmbiguity`] detected in? -#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)] -enum SystemStageSegment { - Parallel, - ExclusiveAtStart, - ExclusiveBeforeCommands, - ExclusiveAtEnd, -} - -impl SystemStageSegment { - pub fn desc(&self) -> &'static str { - match self { - SystemStageSegment::Parallel => "Parallel systems", - SystemStageSegment::ExclusiveAtStart => "Exclusive systems at start of stage", - SystemStageSegment::ExclusiveBeforeCommands => { - "Exclusive systems before commands of stage" - } - SystemStageSegment::ExclusiveAtEnd => "Exclusive systems at end of stage", - } - } -} - -impl SystemOrderAmbiguity { - fn from_raw( - system_a_index: usize, - system_b_index: usize, - component_ids: Vec, - segment: SystemStageSegment, - stage: &SystemStage, - world: &World, - ) -> Self { - use SystemStageSegment::*; - - let systems = match segment { - Parallel => stage.parallel_systems(), - ExclusiveAtStart => stage.exclusive_at_start_systems(), - ExclusiveBeforeCommands => stage.exclusive_before_commands_systems(), - ExclusiveAtEnd => stage.exclusive_at_end_systems(), - }; - let mut system_names = [ - systems[system_a_index].name().to_string(), - systems[system_b_index].name().to_string(), - ]; - system_names.sort(); - - let mut conflicts: Vec<_> = component_ids - .iter() - .map(|id| world.components().get_info(*id).unwrap().name().to_owned()) - .collect(); - conflicts.sort(); - - Self { - system_names, - conflicts, - segment, - } - } -} - -impl SystemStage { - /// Logs execution order ambiguities between systems. - /// - /// The output may be incorrect if this stage has not been initialized with `world`. - pub fn report_ambiguities(&self, world: &World) { - debug_assert!(!self.systems_modified); - use std::fmt::Write; - let ambiguities = self.ambiguities(world); - if !ambiguities.is_empty() { - let mut string = "Execution order ambiguities detected, you might want to \ - add an explicit dependency relation between some of these systems:\n" - .to_owned(); - - let mut last_segment_kind = None; - for SystemOrderAmbiguity { - system_names: [system_a, system_b], - conflicts, - segment, - } in &ambiguities - { - // If the ambiguity occurred in a different segment than the previous one, write a header for the segment. - if last_segment_kind != Some(segment) { - writeln!(string, " * {}:", segment.desc()).unwrap(); - last_segment_kind = Some(segment); - } - - writeln!(string, " -- {system_a:?} and {system_b:?}").unwrap(); - - if !conflicts.is_empty() { - writeln!(string, " conflicts: {conflicts:?}").unwrap(); - } - } - - info!("{}", string); - } - } - - /// Returns all execution order ambiguities between systems. - /// - /// Returns 4 vectors of ambiguities for each stage, in the following order: - /// - parallel - /// - exclusive at start, - /// - exclusive before commands - /// - exclusive at end - /// - /// The result may be incorrect if this stage has not been initialized with `world`. - fn ambiguities(&self, world: &World) -> Vec { - let parallel = find_ambiguities(&self.parallel).into_iter().map( - |(system_a_index, system_b_index, component_ids)| { - SystemOrderAmbiguity::from_raw( - system_a_index, - system_b_index, - component_ids.to_vec(), - SystemStageSegment::Parallel, - self, - world, - ) - }, - ); - - let at_start = find_ambiguities(&self.exclusive_at_start).into_iter().map( - |(system_a_index, system_b_index, component_ids)| { - SystemOrderAmbiguity::from_raw( - system_a_index, - system_b_index, - component_ids, - SystemStageSegment::ExclusiveAtStart, - self, - world, - ) - }, - ); - - let before_commands = find_ambiguities(&self.exclusive_before_commands) - .into_iter() - .map(|(system_a_index, system_b_index, component_ids)| { - SystemOrderAmbiguity::from_raw( - system_a_index, - system_b_index, - component_ids, - SystemStageSegment::ExclusiveBeforeCommands, - self, - world, - ) - }); - - let at_end = find_ambiguities(&self.exclusive_at_end).into_iter().map( - |(system_a_index, system_b_index, component_ids)| { - SystemOrderAmbiguity::from_raw( - system_a_index, - system_b_index, - component_ids, - SystemStageSegment::ExclusiveAtEnd, - self, - world, - ) - }, - ); - - let mut ambiguities: Vec<_> = at_start - .chain(parallel) - .chain(before_commands) - .chain(at_end) - .collect(); - ambiguities.sort(); - ambiguities - } - - /// Returns the number of system order ambiguities between systems in this stage. - /// - /// The result may be incorrect if this stage has not been initialized with `world`. - #[cfg(test)] - fn ambiguity_count(&self, world: &World) -> usize { - self.ambiguities(world).len() - } -} - -/// Returns vector containing all pairs of indices of systems with ambiguous execution order, -/// along with specific components that have triggered the warning. -/// Systems must be topologically sorted beforehand. -fn find_ambiguities(systems: &[SystemContainer]) -> Vec<(usize, usize, Vec)> { - // Check if we should ignore ambiguities between `system_a` and `system_b`. - fn should_ignore(system_a: &SystemContainer, system_b: &SystemContainer) -> bool { - fn should_ignore_inner( - system_a_detection: &AmbiguityDetection, - system_b_labels: &[SystemLabelId], - ) -> bool { - match system_a_detection { - AmbiguityDetection::Check => false, - AmbiguityDetection::IgnoreAll => true, - AmbiguityDetection::IgnoreWithLabel(labels) => { - labels.iter().any(|l| system_b_labels.contains(l)) - } - } - } - should_ignore_inner(&system_a.ambiguity_detection, system_b.labels()) - || should_ignore_inner(&system_b.ambiguity_detection, system_a.labels()) - } - - let mut all_dependencies = Vec::::with_capacity(systems.len()); - let mut all_dependants = Vec::::with_capacity(systems.len()); - for (index, container) in systems.iter().enumerate() { - let mut dependencies = FixedBitSet::with_capacity(systems.len()); - for &dependency in container.dependencies() { - dependencies.union_with(&all_dependencies[dependency]); - dependencies.insert(dependency); - all_dependants[dependency].insert(index); - } - - all_dependants.push(FixedBitSet::with_capacity(systems.len())); - all_dependencies.push(dependencies); - } - for index in (0..systems.len()).rev() { - let mut dependants = FixedBitSet::with_capacity(systems.len()); - for dependant in all_dependants[index].ones() { - dependants.union_with(&all_dependants[dependant]); - dependants.insert(dependant); - } - all_dependants[index] = dependants; - } - let all_relations = all_dependencies - .into_iter() - .zip(all_dependants.into_iter()) - .enumerate() - .map(|(index, (dependencies, dependants))| { - let mut relations = FixedBitSet::with_capacity(systems.len()); - relations.union_with(&dependencies); - relations.union_with(&dependants); - relations.insert(index); - relations - }) - .collect::>(); - let mut ambiguities = Vec::new(); - let full_bitset: FixedBitSet = (0..systems.len()).collect(); - let mut processed = FixedBitSet::with_capacity(systems.len()); - for (index_a, relations) in all_relations.into_iter().enumerate() { - // TODO: prove that `.take(index_a)` would be correct here, and uncomment it if so. - for index_b in full_bitset.difference(&relations) - // .take(index_a) - { - if !processed.contains(index_b) && !should_ignore(&systems[index_a], &systems[index_b]) - { - let system_a = &systems[index_a]; - let system_b = &systems[index_b]; - if system_a.is_exclusive() || system_b.is_exclusive() { - ambiguities.push((index_a, index_b, Vec::new())); - } else { - let a_access = systems[index_a].component_access(); - let b_access = systems[index_b].component_access(); - let conflicts = a_access.get_conflicts(b_access); - if !conflicts.is_empty() { - ambiguities.push((index_a, index_b, conflicts)); - } - } - } - } - processed.insert(index_a); - } - ambiguities -} - -#[cfg(test)] -mod tests { - // Required to make the derive macro behave - use crate as bevy_ecs; - use crate::event::Events; - use crate::prelude::*; - - #[derive(Resource)] - struct R; - - #[derive(Component)] - struct A; - - #[derive(Component)] - struct B; - - // An event type - struct E; - - fn empty_system() {} - fn res_system(_res: Res) {} - fn resmut_system(_res: ResMut) {} - fn nonsend_system(_ns: NonSend) {} - fn nonsendmut_system(_ns: NonSendMut) {} - fn read_component_system(_query: Query<&A>) {} - fn write_component_system(_query: Query<&mut A>) {} - fn with_filtered_component_system(_query: Query<&mut A, With>) {} - fn without_filtered_component_system(_query: Query<&mut A, Without>) {} - fn event_reader_system(_reader: EventReader) {} - fn event_writer_system(_writer: EventWriter) {} - fn event_resource_system(_events: ResMut>) {} - fn read_world_system(_world: &World) {} - fn write_world_system(_world: &mut World) {} - - // Tests for conflict detection - - #[test] - fn one_of_everything() { - let mut world = World::new(); - world.insert_resource(R); - world.spawn(A); - world.init_resource::>(); - - let mut test_stage = SystemStage::parallel(); - test_stage - // nonsendmut system deliberately conflicts with resmut system - .add_system(resmut_system) - .add_system(write_component_system) - .add_system(event_writer_system); - - test_stage.run(&mut world); - - assert_eq!(test_stage.ambiguity_count(&world), 0); - } - - #[test] - fn read_only() { - let mut world = World::new(); - world.insert_resource(R); - world.insert_non_send_resource(R); - world.spawn(A); - world.init_resource::>(); - - let mut test_stage = SystemStage::parallel(); - test_stage - .add_system(empty_system) - .add_system(empty_system) - .add_system(res_system) - .add_system(res_system) - .add_system(nonsend_system) - .add_system(nonsend_system) - .add_system(read_component_system) - .add_system(read_component_system) - .add_system(event_reader_system) - .add_system(event_reader_system) - .add_system(read_world_system) - .add_system(read_world_system); - - test_stage.run(&mut world); - - assert_eq!(test_stage.ambiguity_count(&world), 0); - } - - #[test] - fn read_world() { - let mut world = World::new(); - world.insert_resource(R); - world.spawn(A); - world.init_resource::>(); - - let mut test_stage = SystemStage::parallel(); - test_stage - .add_system(resmut_system) - .add_system(write_component_system) - .add_system(event_writer_system) - .add_system(read_world_system); - - test_stage.run(&mut world); - - assert_eq!(test_stage.ambiguity_count(&world), 3); - } - - #[test] - fn resources() { - let mut world = World::new(); - world.insert_resource(R); - - let mut test_stage = SystemStage::parallel(); - test_stage.add_system(resmut_system).add_system(res_system); - - test_stage.run(&mut world); - - assert_eq!(test_stage.ambiguity_count(&world), 1); - } - - #[test] - fn nonsend() { - let mut world = World::new(); - world.insert_resource(R); - world.insert_non_send_resource(R); - - let mut test_stage = SystemStage::parallel(); - test_stage - .add_system(nonsendmut_system) - .add_system(nonsend_system); - - test_stage.run(&mut world); - - assert_eq!(test_stage.ambiguity_count(&world), 1); - } - - #[test] - fn components() { - let mut world = World::new(); - world.spawn(A); - - let mut test_stage = SystemStage::parallel(); - test_stage - .add_system(read_component_system) - .add_system(write_component_system); - - test_stage.run(&mut world); - - assert_eq!(test_stage.ambiguity_count(&world), 1); - } - - #[test] - #[ignore = "Known failing but fix is non-trivial: https://github.com/bevyengine/bevy/issues/4381"] - fn filtered_components() { - let mut world = World::new(); - world.spawn(A); - - let mut test_stage = SystemStage::parallel(); - test_stage - .add_system(with_filtered_component_system) - .add_system(without_filtered_component_system); - - test_stage.run(&mut world); - - assert_eq!(test_stage.ambiguity_count(&world), 0); - } - - #[test] - fn events() { - let mut world = World::new(); - world.init_resource::>(); - - let mut test_stage = SystemStage::parallel(); - test_stage - // All of these systems clash - .add_system(event_reader_system) - .add_system(event_writer_system) - .add_system(event_resource_system); - - test_stage.run(&mut world); - - assert_eq!(test_stage.ambiguity_count(&world), 3); - } - - #[test] - fn exclusive() { - let mut world = World::new(); - world.insert_resource(R); - world.spawn(A); - world.init_resource::>(); - - let mut test_stage = SystemStage::parallel(); - test_stage - // All 3 of these conflict with each other - .add_system(write_world_system) - .add_system(write_world_system.at_end()) - .add_system(res_system.at_start()) - // These do not, as they're in different segments of the stage - .add_system(write_world_system.at_start()) - .add_system(write_world_system.before_commands()); - - test_stage.run(&mut world); - - assert_eq!(test_stage.ambiguity_count(&world), 3); - } - - // Tests for silencing and resolving ambiguities - - #[test] - fn before_and_after() { - let mut world = World::new(); - world.init_resource::>(); - - let mut test_stage = SystemStage::parallel(); - test_stage - .add_system(event_reader_system.before(event_writer_system)) - .add_system(event_writer_system) - .add_system(event_resource_system.after(event_writer_system)); - - test_stage.run(&mut world); - - assert_eq!(test_stage.ambiguity_count(&world), 0); - } - - #[test] - fn ignore_all_ambiguities() { - let mut world = World::new(); - world.insert_resource(R); - world.insert_non_send_resource(R); - - let mut test_stage = SystemStage::parallel(); - test_stage - .add_system(resmut_system.ignore_all_ambiguities()) - .add_system(res_system) - .add_system(nonsend_system); - - test_stage.run(&mut world); - - assert_eq!(test_stage.ambiguity_count(&world), 0); - } - - #[test] - fn ambiguous_with_label() { - let mut world = World::new(); - world.insert_resource(R); - world.insert_non_send_resource(R); - - #[derive(SystemLabel)] - struct IgnoreMe; - - let mut test_stage = SystemStage::parallel(); - test_stage - .add_system(resmut_system.ambiguous_with(IgnoreMe)) - .add_system(res_system.label(IgnoreMe)) - .add_system(nonsend_system.label(IgnoreMe)); - - test_stage.run(&mut world); - - assert_eq!(test_stage.ambiguity_count(&world), 0); - } - - #[test] - fn ambiguous_with_system() { - let mut world = World::new(); - - let mut test_stage = SystemStage::parallel(); - test_stage - .add_system(write_component_system.ambiguous_with(read_component_system)) - .add_system(read_component_system); - - test_stage.run(&mut world); - - assert_eq!(test_stage.ambiguity_count(&world), 0); - } - - fn system_a(_res: ResMut) {} - fn system_b(_res: ResMut) {} - fn system_c(_res: ResMut) {} - fn system_d(_res: ResMut) {} - fn system_e(_res: ResMut) {} - - // Tests that the correct ambiguities were reported in the correct order. - #[test] - fn correct_ambiguities() { - use super::*; - - let mut world = World::new(); - world.insert_resource(R); - - let mut test_stage = SystemStage::parallel(); - test_stage - .add_system(system_a) - .add_system(system_b) - .add_system(system_c.ignore_all_ambiguities()) - .add_system(system_d.ambiguous_with(system_b)) - .add_system(system_e.after(system_a)); - - test_stage.run(&mut world); - - let ambiguities = test_stage.ambiguities(&world); - assert_eq!( - ambiguities, - vec![ - SystemOrderAmbiguity { - system_names: [ - "bevy_ecs::schedule::ambiguity_detection::tests::system_a".to_string(), - "bevy_ecs::schedule::ambiguity_detection::tests::system_b".to_string() - ], - conflicts: vec!["bevy_ecs::schedule::ambiguity_detection::tests::R".to_string()], - segment: SystemStageSegment::Parallel, - }, - SystemOrderAmbiguity { - system_names: [ - "bevy_ecs::schedule::ambiguity_detection::tests::system_a".to_string(), - "bevy_ecs::schedule::ambiguity_detection::tests::system_d".to_string() - ], - conflicts: vec!["bevy_ecs::schedule::ambiguity_detection::tests::R".to_string()], - segment: SystemStageSegment::Parallel, - }, - SystemOrderAmbiguity { - system_names: [ - "bevy_ecs::schedule::ambiguity_detection::tests::system_b".to_string(), - "bevy_ecs::schedule::ambiguity_detection::tests::system_e".to_string() - ], - conflicts: vec!["bevy_ecs::schedule::ambiguity_detection::tests::R".to_string()], - segment: SystemStageSegment::Parallel, - }, - SystemOrderAmbiguity { - system_names: [ - "bevy_ecs::schedule::ambiguity_detection::tests::system_d".to_string(), - "bevy_ecs::schedule::ambiguity_detection::tests::system_e".to_string() - ], - conflicts: vec!["bevy_ecs::schedule::ambiguity_detection::tests::R".to_string()], - segment: SystemStageSegment::Parallel, - }, - ] - ); - } -} diff --git a/crates/bevy_ecs/src/schedule/executor.rs b/crates/bevy_ecs/src/schedule/executor.rs deleted file mode 100644 index ef0cb45fb229d..0000000000000 --- a/crates/bevy_ecs/src/schedule/executor.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::{schedule::SystemContainer, world::World}; -use core::fmt::Debug; -use downcast_rs::{impl_downcast, Downcast}; - -pub trait ParallelSystemExecutor: Downcast + Send + Sync { - /// Called by `SystemStage` whenever `systems` have been changed. - fn rebuild_cached_data(&mut self, systems: &[SystemContainer]); - - fn run_systems(&mut self, systems: &mut [SystemContainer], world: &mut World); -} - -impl Debug for dyn ParallelSystemExecutor { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "dyn ParallelSystemExecutor") - } -} - -impl_downcast!(ParallelSystemExecutor); - -#[derive(Debug, Default)] -pub struct SingleThreadedExecutor; - -impl ParallelSystemExecutor for SingleThreadedExecutor { - fn rebuild_cached_data(&mut self, _: &[SystemContainer]) {} - - fn run_systems(&mut self, systems: &mut [SystemContainer], world: &mut World) { - for system in systems { - if system.should_run() { - #[cfg(feature = "trace")] - let _system_span = - bevy_utils::tracing::info_span!("system", name = &*system.name()).entered(); - system.system_mut().run((), world); - } - } - } -} diff --git a/crates/bevy_ecs/src/schedule/executor_parallel.rs b/crates/bevy_ecs/src/schedule/executor_parallel.rs deleted file mode 100644 index 68dd1f1ea798d..0000000000000 --- a/crates/bevy_ecs/src/schedule/executor_parallel.rs +++ /dev/null @@ -1,564 +0,0 @@ -use crate::{ - archetype::ArchetypeComponentId, - query::Access, - schedule::{ParallelSystemExecutor, SystemContainer}, - world::World, -}; -use async_channel::{Receiver, Sender}; -use bevy_tasks::{ComputeTaskPool, Scope, TaskPool}; -#[cfg(feature = "trace")] -use bevy_utils::tracing::Instrument; -use event_listener::Event; -use fixedbitset::FixedBitSet; - -#[cfg(test)] -use scheduling_event::*; - -struct SystemSchedulingMetadata { - /// Used to signal the system's task to start the system. - start: Event, - /// Indices of systems that depend on this one, used to decrement their - /// dependency counters when this system finishes. - dependants: Vec, - /// Total amount of dependencies this system has. - dependencies_total: usize, - /// Amount of unsatisfied dependencies, when it reaches 0 the system is queued to be started. - dependencies_now: usize, - /// Archetype-component access information. - archetype_component_access: Access, - /// Whether or not this system is send-able - is_send: bool, -} - -pub struct ParallelExecutor { - /// Cached metadata of every system. - system_metadata: Vec, - /// Used by systems to notify the executor that they have finished. - finish_sender: Sender, - /// Receives finish events from systems. - finish_receiver: Receiver, - /// Systems that should be started at next opportunity. - queued: FixedBitSet, - /// Systems that are currently running. - running: FixedBitSet, - /// Whether a non-send system is currently running. - non_send_running: bool, - /// Systems that should run this iteration. - should_run: FixedBitSet, - /// Compound archetype-component access information of currently running systems. - active_archetype_component_access: Access, - /// Scratch space to avoid reallocating a vector when updating dependency counters. - dependants_scratch: Vec, - #[cfg(test)] - events_sender: Option>, -} - -impl Default for ParallelExecutor { - fn default() -> Self { - // Using a bounded channel here as it avoids allocations when signaling - // and generally remains hotter in memory. It'll take 128 systems completing - // before the parallel executor runs before this overflows. If it overflows - // all systems will just suspend until the parallel executor runs. - let (finish_sender, finish_receiver) = async_channel::bounded(128); - Self { - system_metadata: Default::default(), - finish_sender, - finish_receiver, - queued: Default::default(), - running: Default::default(), - non_send_running: false, - should_run: Default::default(), - active_archetype_component_access: Default::default(), - dependants_scratch: Default::default(), - #[cfg(test)] - events_sender: None, - } - } -} - -impl ParallelSystemExecutor for ParallelExecutor { - fn rebuild_cached_data(&mut self, systems: &[SystemContainer]) { - self.system_metadata.clear(); - self.queued.grow(systems.len()); - self.running.grow(systems.len()); - self.should_run.grow(systems.len()); - - // Construct scheduling data for systems. - for container in systems { - let dependencies_total = container.dependencies().len(); - let system = container.system(); - self.system_metadata.push(SystemSchedulingMetadata { - start: Event::new(), - dependants: vec![], - dependencies_total, - dependencies_now: 0, - is_send: system.is_send(), - archetype_component_access: Default::default(), - }); - } - // Populate the dependants lists in the scheduling metadata. - for (dependant, container) in systems.iter().enumerate() { - for dependency in container.dependencies() { - self.system_metadata[*dependency].dependants.push(dependant); - } - } - } - - fn run_systems(&mut self, systems: &mut [SystemContainer], world: &mut World) { - #[cfg(test)] - if self.events_sender.is_none() { - let (sender, receiver) = async_channel::unbounded::(); - world.insert_resource(SchedulingEvents(receiver)); - self.events_sender = Some(sender); - } - - { - #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!("update_archetypes").entered(); - for (index, container) in systems.iter_mut().enumerate() { - let meta = &mut self.system_metadata[index]; - let system = container.system_mut(); - system.update_archetype_component_access(world); - meta.archetype_component_access - .extend(system.archetype_component_access()); - } - } - - ComputeTaskPool::init(TaskPool::default).scope(|scope| { - self.prepare_systems(scope, systems, world); - if self.should_run.count_ones(..) == 0 { - return; - } - let parallel_executor = async { - // All systems have been ran if there are no queued or running systems. - while 0 != self.queued.count_ones(..) + self.running.count_ones(..) { - self.process_queued_systems(); - // Avoid deadlocking if no systems were actually started. - if self.running.count_ones(..) != 0 { - // Wait until at least one system has finished. - let index = self - .finish_receiver - .recv() - .await - .unwrap_or_else(|error| unreachable!("{}", error)); - self.process_finished_system(index); - // Gather other systems than may have finished. - while let Ok(index) = self.finish_receiver.try_recv() { - self.process_finished_system(index); - } - // At least one system has finished, so active access is outdated. - self.rebuild_active_access(); - } - self.update_counters_and_queue_systems(); - } - }; - #[cfg(feature = "trace")] - let span = bevy_utils::tracing::info_span!("parallel executor"); - #[cfg(feature = "trace")] - let parallel_executor = parallel_executor.instrument(span); - scope.spawn(parallel_executor); - }); - } -} - -impl ParallelExecutor { - /// Populates `should_run` bitset, spawns tasks for systems that should run this iteration, - /// queues systems with no dependencies to run (or skip) at next opportunity. - fn prepare_systems<'scope>( - &mut self, - scope: &Scope<'_, 'scope, ()>, - systems: &'scope mut [SystemContainer], - world: &'scope World, - ) { - // These are used as a part of a unit test. - #[cfg(test)] - let mut started_systems = 0; - #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!("prepare_systems").entered(); - self.should_run.clear(); - for (index, (system_data, system)) in - self.system_metadata.iter_mut().zip(systems).enumerate() - { - let should_run = system.should_run(); - let can_start = should_run - && system_data.dependencies_total == 0 - && Self::can_start_now( - self.non_send_running, - system_data, - &self.active_archetype_component_access, - ); - - // Queue the system if it has no dependencies, otherwise reset its dependency counter. - if system_data.dependencies_total == 0 { - if !can_start { - self.queued.insert(index); - } - } else { - system_data.dependencies_now = system_data.dependencies_total; - } - - if !should_run { - continue; - } - - // Spawn the system task. - self.should_run.insert(index); - let finish_sender = self.finish_sender.clone(); - let system = system.system_mut(); - #[cfg(feature = "trace")] // NB: outside the task to get the TLS current span - let system_span = bevy_utils::tracing::info_span!("system", name = &*system.name()); - #[cfg(feature = "trace")] - let overhead_span = - bevy_utils::tracing::info_span!("system overhead", name = &*system.name()); - - let mut run = move || { - #[cfg(feature = "trace")] - let _system_guard = system_span.enter(); - // SAFETY: the executor prevents two systems with conflicting access from running simultaneously. - unsafe { system.run_unsafe((), world) }; - }; - - if can_start { - let task = async move { - run(); - // This will never panic: - // - The channel is never closed or dropped. - // - Overflowing the bounded size will just suspend until - // there is capacity. - finish_sender - .send(index) - .await - .unwrap_or_else(|error| unreachable!("{}", error)); - }; - - #[cfg(feature = "trace")] - let task = task.instrument(overhead_span); - if system_data.is_send { - scope.spawn(task); - } else { - scope.spawn_on_scope(task); - } - - #[cfg(test)] - { - started_systems += 1; - } - - self.running.insert(index); - if !system_data.is_send { - self.non_send_running = true; - } - // Add this system's access information to the active access information. - self.active_archetype_component_access - .extend(&system_data.archetype_component_access); - } else { - let start_listener = system_data.start.listen(); - let task = async move { - start_listener.await; - run(); - // This will never panic: - // - The channel is never closed or dropped. - // - Overflowing the bounded size will just suspend until - // there is capacity. - finish_sender - .send(index) - .await - .unwrap_or_else(|error| unreachable!("{}", error)); - }; - - #[cfg(feature = "trace")] - let task = task.instrument(overhead_span); - if system_data.is_send { - scope.spawn(task); - } else { - scope.spawn_on_scope(task); - } - } - } - #[cfg(test)] - if started_systems != 0 { - self.emit_event(SchedulingEvent::StartedSystems(started_systems)); - } - } - - /// Determines if the system with given index has no conflicts with already running systems. - #[inline] - fn can_start_now( - non_send_running: bool, - system_data: &SystemSchedulingMetadata, - active_archetype_component_access: &Access, - ) -> bool { - // Non-send systems are considered conflicting with each other. - (!non_send_running || system_data.is_send) - && system_data - .archetype_component_access - .is_compatible(active_archetype_component_access) - } - - /// Starts all non-conflicting queued systems, moves them from `queued` to `running`, - /// adds their access information to active access information; - /// processes queued systems that shouldn't run this iteration as completed immediately. - fn process_queued_systems(&mut self) { - // These are used as a part of a unit test as seen in `process_queued_systems`. - // Removing them will cause the test to fail. - #[cfg(test)] - let mut started_systems = 0; - for index in self.queued.ones() { - // If the system shouldn't actually run this iteration, process it as completed - // immediately; otherwise, check for conflicts and signal its task to start. - let system_metadata = &self.system_metadata[index]; - if !self.should_run[index] { - self.dependants_scratch.extend(&system_metadata.dependants); - } else if Self::can_start_now( - self.non_send_running, - system_metadata, - &self.active_archetype_component_access, - ) { - #[cfg(test)] - { - started_systems += 1; - } - system_metadata.start.notify_additional_relaxed(1); - self.running.insert(index); - if !system_metadata.is_send { - self.non_send_running = true; - } - // Add this system's access information to the active access information. - self.active_archetype_component_access - .extend(&system_metadata.archetype_component_access); - } - } - #[cfg(test)] - if started_systems != 0 { - self.emit_event(SchedulingEvent::StartedSystems(started_systems)); - } - // Remove now running systems from the queue. - self.queued.difference_with(&self.running); - // Remove immediately processed systems from the queue. - self.queued.intersect_with(&self.should_run); - } - - /// Unmarks the system give index as running, caches indices of its dependants - /// in the `dependants_scratch`. - fn process_finished_system(&mut self, index: usize) { - let system_data = &self.system_metadata[index]; - if !system_data.is_send { - self.non_send_running = false; - } - self.running.set(index, false); - self.dependants_scratch.extend(&system_data.dependants); - } - - /// Discards active access information and builds it again using currently - /// running systems' access information. - fn rebuild_active_access(&mut self) { - self.active_archetype_component_access.clear(); - for index in self.running.ones() { - self.active_archetype_component_access - .extend(&self.system_metadata[index].archetype_component_access); - } - } - - /// Drains `dependants_scratch`, decrementing dependency counters and enqueueing any - /// systems that become able to run. - fn update_counters_and_queue_systems(&mut self) { - for index in self.dependants_scratch.drain(..) { - let dependant_data = &mut self.system_metadata[index]; - dependant_data.dependencies_now -= 1; - if dependant_data.dependencies_now == 0 { - self.queued.insert(index); - } - } - } - - #[cfg(test)] - fn emit_event(&self, event: SchedulingEvent) { - let _ = self.events_sender.as_ref().unwrap().try_send(event); - } -} - -#[cfg(test)] -mod scheduling_event { - use crate as bevy_ecs; - use crate::system::Resource; - use async_channel::Receiver; - - #[derive(Debug, PartialEq, Eq)] - pub(super) enum SchedulingEvent { - StartedSystems(usize), - } - - #[derive(Resource)] - pub(super) struct SchedulingEvents(pub(crate) Receiver); -} - -#[cfg(test)] -#[cfg(test)] -mod tests { - use crate::{ - self as bevy_ecs, - component::Component, - schedule::{ - executor_parallel::scheduling_event::*, SingleThreadedExecutor, Stage, SystemStage, - }, - system::{NonSend, Query, Res, ResMut, Resource}, - world::World, - }; - - use SchedulingEvent::StartedSystems; - - #[derive(Component)] - struct W(T); - #[derive(Resource, Default)] - struct Counter(usize); - - fn receive_events(world: &World) -> Vec { - let mut events = Vec::new(); - while let Ok(event) = world.resource::().0.try_recv() { - events.push(event); - } - events - } - - #[test] - fn trivial() { - let mut world = World::new(); - fn wants_for_nothing() {} - let mut stage = SystemStage::parallel() - .with_system(wants_for_nothing) - .with_system(wants_for_nothing) - .with_system(wants_for_nothing); - stage.run(&mut world); - stage.run(&mut world); - assert_eq!( - receive_events(&world), - vec![StartedSystems(3), StartedSystems(3),] - ); - } - - #[test] - fn resources() { - let mut world = World::new(); - world.init_resource::(); - fn wants_mut(_: ResMut) {} - fn wants_ref(_: Res) {} - let mut stage = SystemStage::parallel() - .with_system(wants_mut) - .with_system(wants_mut); - stage.run(&mut world); - assert_eq!( - receive_events(&world), - vec![StartedSystems(1), StartedSystems(1),] - ); - let mut stage = SystemStage::parallel() - .with_system(wants_mut) - .with_system(wants_ref); - stage.run(&mut world); - assert_eq!( - receive_events(&world), - vec![StartedSystems(1), StartedSystems(1),] - ); - let mut stage = SystemStage::parallel() - .with_system(wants_ref) - .with_system(wants_ref); - stage.run(&mut world); - assert_eq!(receive_events(&world), vec![StartedSystems(2),]); - } - - #[test] - fn queries() { - let mut world = World::new(); - world.spawn(W(0usize)); - fn wants_mut(_: Query<&mut W>) {} - fn wants_ref(_: Query<&W>) {} - let mut stage = SystemStage::parallel() - .with_system(wants_mut) - .with_system(wants_mut); - stage.run(&mut world); - assert_eq!( - receive_events(&world), - vec![StartedSystems(1), StartedSystems(1),] - ); - let mut stage = SystemStage::parallel() - .with_system(wants_mut) - .with_system(wants_ref); - stage.run(&mut world); - assert_eq!( - receive_events(&world), - vec![StartedSystems(1), StartedSystems(1),] - ); - let mut stage = SystemStage::parallel() - .with_system(wants_ref) - .with_system(wants_ref); - stage.run(&mut world); - assert_eq!(receive_events(&world), vec![StartedSystems(2),]); - let mut world = World::new(); - world.spawn((W(0usize), W(0u32), W(0f32))); - fn wants_mut_usize(_: Query<(&mut W, &W)>) {} - fn wants_mut_u32(_: Query<(&mut W, &W)>) {} - let mut stage = SystemStage::parallel() - .with_system(wants_mut_usize) - .with_system(wants_mut_u32); - stage.run(&mut world); - assert_eq!(receive_events(&world), vec![StartedSystems(2),]); - } - - #[test] - fn world() { - let mut world = World::new(); - world.spawn(W(0usize)); - fn wants_world(_: &World) {} - fn wants_mut(_: Query<&mut W>) {} - let mut stage = SystemStage::parallel() - .with_system(wants_mut) - .with_system(wants_mut); - stage.run(&mut world); - assert_eq!( - receive_events(&world), - vec![StartedSystems(1), StartedSystems(1),] - ); - let mut stage = SystemStage::parallel() - .with_system(wants_mut) - .with_system(wants_world); - stage.run(&mut world); - assert_eq!( - receive_events(&world), - vec![StartedSystems(1), StartedSystems(1),] - ); - let mut stage = SystemStage::parallel() - .with_system(wants_world) - .with_system(wants_world); - stage.run(&mut world); - assert_eq!(receive_events(&world), vec![StartedSystems(2),]); - } - - #[test] - fn non_send_resource() { - use std::thread; - let mut world = World::new(); - world.insert_non_send_resource(thread::current().id()); - fn non_send(thread_id: NonSend) { - assert_eq!(thread::current().id(), *thread_id); - } - fn empty() {} - let mut stage = SystemStage::parallel() - .with_system(non_send) - .with_system(non_send) - .with_system(empty) - .with_system(empty) - .with_system(non_send) - .with_system(non_send); - stage.run(&mut world); - assert_eq!( - receive_events(&world), - vec![ - StartedSystems(3), - StartedSystems(1), - StartedSystems(1), - StartedSystems(1), - ] - ); - stage.set_executor(Box::::default()); - stage.run(&mut world); - } -} diff --git a/crates/bevy_ecs/src/schedule/graph_utils.rs b/crates/bevy_ecs/src/schedule/graph_utils.rs deleted file mode 100644 index 88cda8e2042dd..0000000000000 --- a/crates/bevy_ecs/src/schedule/graph_utils.rs +++ /dev/null @@ -1,126 +0,0 @@ -use bevy_utils::{tracing::warn, HashMap, HashSet}; -use fixedbitset::FixedBitSet; -use std::{borrow::Cow, fmt::Debug, hash::Hash}; - -pub enum DependencyGraphError { - GraphCycles(Vec<(usize, Labels)>), -} - -pub trait GraphNode { - type Label; - fn name(&self) -> Cow<'static, str>; - fn labels(&self) -> &[Self::Label]; - fn before(&self) -> &[Self::Label]; - fn after(&self) -> &[Self::Label]; -} - -/// Constructs a dependency graph of given nodes. -pub fn build_dependency_graph( - nodes: &[Node], -) -> HashMap>> -where - Node: GraphNode, - Node::Label: Debug + Clone + Eq + Hash, -{ - let mut labels = HashMap::::default(); - for (label, index) in nodes.iter().enumerate().flat_map(|(index, container)| { - container - .labels() - .iter() - .cloned() - .map(move |label| (label, index)) - }) { - labels - .entry(label) - .or_insert_with(|| FixedBitSet::with_capacity(nodes.len())) - .insert(index); - } - let mut graph = HashMap::with_capacity_and_hasher(nodes.len(), Default::default()); - for (index, node) in nodes.iter().enumerate() { - let dependencies = graph.entry(index).or_insert_with(HashMap::default); - for label in node.after() { - match labels.get(label) { - Some(new_dependencies) => { - for dependency in new_dependencies.ones() { - dependencies - .entry(dependency) - .or_insert_with(HashSet::default) - .insert(label.clone()); - } - } - None => warn!( - // TODO: plumb this as proper output? - "{} wants to be after unknown label: {:?}", - nodes[index].name(), - label - ), - } - } - for label in node.before() { - match labels.get(label) { - Some(dependants) => { - for dependant in dependants.ones() { - graph - .entry(dependant) - .or_insert_with(HashMap::default) - .entry(index) - .or_insert_with(HashSet::default) - .insert(label.clone()); - } - } - None => warn!( - "{} wants to be before unknown label: {:?}", - nodes[index].name(), - label - ), - } - } - } - graph -} - -/// Generates a topological order for the given graph. -pub fn topological_order( - graph: &HashMap>, -) -> Result, DependencyGraphError> { - fn check_if_cycles_and_visit( - node: &usize, - graph: &HashMap>, - sorted: &mut Vec, - unvisited: &mut HashSet, - current: &mut Vec, - ) -> bool { - if current.contains(node) { - return true; - } else if !unvisited.remove(node) { - return false; - } - current.push(*node); - for dependency in graph.get(node).unwrap().keys() { - if check_if_cycles_and_visit(dependency, graph, sorted, unvisited, current) { - return true; - } - } - sorted.push(*node); - current.pop(); - false - } - let mut sorted = Vec::with_capacity(graph.len()); - let mut current = Vec::with_capacity(graph.len()); - let mut unvisited = HashSet::with_capacity_and_hasher(graph.len(), Default::default()); - unvisited.extend(graph.keys().cloned()); - while let Some(node) = unvisited.iter().next().cloned() { - if check_if_cycles_and_visit(&node, graph, &mut sorted, &mut unvisited, &mut current) { - let mut cycle = Vec::new(); - let last_window = [*current.last().unwrap(), current[0]]; - let mut windows = current - .windows(2) - .chain(std::iter::once(&last_window as &[usize])); - while let Some(&[dependant, dependency]) = windows.next() { - cycle.push((dependant, graph[&dependant][&dependency].clone())); - } - return Err(DependencyGraphError::GraphCycles(cycle)); - } - } - Ok(sorted) -} diff --git a/crates/bevy_ecs/src/schedule/label.rs b/crates/bevy_ecs/src/schedule/label.rs deleted file mode 100644 index c2f4d1fbfab7e..0000000000000 --- a/crates/bevy_ecs/src/schedule/label.rs +++ /dev/null @@ -1,21 +0,0 @@ -pub use bevy_ecs_macros::{RunCriteriaLabel, StageLabel, SystemLabel}; -use bevy_utils::define_label; - -define_label!( - /// A strongly-typed class of labels used to identify [`Stage`](crate::schedule::Stage)s. - StageLabel, - /// Strongly-typed identifier for a [`StageLabel`]. - StageLabelId, -); -define_label!( - /// A strongly-typed class of labels used to identify [`System`](crate::system::System)s. - SystemLabel, - /// Strongly-typed identifier for a [`SystemLabel`]. - SystemLabelId, -); -define_label!( - /// A strongly-typed class of labels used to identify [run criteria](crate::schedule::RunCriteria). - RunCriteriaLabel, - /// Strongly-typed identifier for a [`RunCriteriaLabel`]. - RunCriteriaLabelId, -); diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs deleted file mode 100644 index bc78e8f0798ac..0000000000000 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ /dev/null @@ -1,408 +0,0 @@ -//! Tools for controlling system execution. -//! -//! When using Bevy ECS, systems are usually not run directly, but are inserted into a -//! [`Stage`], which then lives within a [`Schedule`]. - -mod ambiguity_detection; -mod executor; -mod executor_parallel; -pub mod graph_utils; -mod label; -mod run_criteria; -mod stage; -mod state; -mod system_container; -mod system_descriptor; -mod system_set; - -pub use executor::*; -pub use executor_parallel::*; -pub use graph_utils::GraphNode; -pub use label::*; -pub use run_criteria::*; -pub use stage::*; -pub use state::*; -pub use system_container::*; -pub use system_descriptor::*; -pub use system_set::*; - -use std::fmt::Debug; - -use crate::{system::IntoSystem, world::World}; -use bevy_utils::HashMap; - -/// A container of [`Stage`]s set to be run in a linear order. -/// -/// Since `Schedule` implements the [`Stage`] trait, it can be inserted into another schedule. -/// In this way, the properties of the child schedule can be set differently from the parent. -/// For example, it can be set to run only once during app execution, while the parent schedule -/// runs indefinitely. -#[derive(Debug, Default)] -pub struct Schedule { - stages: HashMap>, - stage_order: Vec, - run_criteria: BoxedRunCriteria, -} - -impl Schedule { - /// Similar to [`add_stage`](Self::add_stage), but it also returns itself. - #[must_use] - pub fn with_stage(mut self, label: impl StageLabel, stage: S) -> Self { - self.add_stage(label, stage); - self - } - - /// Similar to [`add_stage_after`](Self::add_stage_after), but it also returns itself. - #[must_use] - pub fn with_stage_after( - mut self, - target: impl StageLabel, - label: impl StageLabel, - stage: S, - ) -> Self { - self.add_stage_after(target, label, stage); - self - } - - /// Similar to [`add_stage_before`](Self::add_stage_before), but it also returns itself. - #[must_use] - pub fn with_stage_before( - mut self, - target: impl StageLabel, - label: impl StageLabel, - stage: S, - ) -> Self { - self.add_stage_before(target, label, stage); - self - } - - #[must_use] - pub fn with_run_criteria, P>(mut self, system: S) -> Self { - self.set_run_criteria(system); - self - } - - /// Similar to [`add_system_to_stage`](Self::add_system_to_stage), but it also returns itself. - #[must_use] - pub fn with_system_in_stage( - mut self, - stage_label: impl StageLabel, - system: impl IntoSystemDescriptor, - ) -> Self { - self.add_system_to_stage(stage_label, system); - self - } - - pub fn set_run_criteria, P>(&mut self, system: S) -> &mut Self { - self.run_criteria - .set(Box::new(IntoSystem::into_system(system))); - self - } - - /// Adds the given `stage` at the last position of the schedule. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut schedule = Schedule::default(); - /// // Define a new label for the stage. - /// #[derive(StageLabel)] - /// struct MyStage; - /// // Add a stage with that label to the schedule. - /// schedule.add_stage(MyStage, SystemStage::parallel()); - /// ``` - pub fn add_stage(&mut self, label: impl StageLabel, stage: S) -> &mut Self { - let label = label.as_label(); - self.stage_order.push(label); - let prev = self.stages.insert(label, Box::new(stage)); - assert!(prev.is_none(), "Stage already exists: {label:?}."); - self - } - - /// Adds the given `stage` immediately after the `target` stage. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut schedule = Schedule::default(); - /// # #[derive(StageLabel)] - /// # struct TargetStage; - /// # schedule.add_stage(TargetStage, SystemStage::parallel()); - /// // Define a new label for the stage. - /// #[derive(StageLabel)] - /// struct NewStage; - /// // Add a stage with that label to the schedule. - /// schedule.add_stage_after(TargetStage, NewStage, SystemStage::parallel()); - /// ``` - pub fn add_stage_after( - &mut self, - target: impl StageLabel, - label: impl StageLabel, - stage: S, - ) -> &mut Self { - let label = label.as_label(); - let target = target.as_label(); - let target_index = self - .stage_order - .iter() - .enumerate() - .find(|(_i, stage_label)| **stage_label == target) - .map(|(i, _)| i) - .unwrap_or_else(|| panic!("Target stage does not exist: {target:?}.")); - - self.stage_order.insert(target_index + 1, label); - let prev = self.stages.insert(label, Box::new(stage)); - assert!(prev.is_none(), "Stage already exists: {label:?}."); - self - } - - /// Adds the given `stage` immediately before the `target` stage. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut schedule = Schedule::default(); - /// # #[derive(StageLabel)] - /// # struct TargetStage; - /// # schedule.add_stage(TargetStage, SystemStage::parallel()); - /// # - /// // Define a new, private label for the stage. - /// #[derive(StageLabel)] - /// struct NewStage; - /// // Add a stage with that label to the schedule. - /// schedule.add_stage_before(TargetStage, NewStage, SystemStage::parallel()); - /// ``` - pub fn add_stage_before( - &mut self, - target: impl StageLabel, - label: impl StageLabel, - stage: S, - ) -> &mut Self { - let label = label.as_label(); - let target = target.as_label(); - let target_index = self - .stage_order - .iter() - .enumerate() - .find(|(_i, stage_label)| **stage_label == target) - .map(|(i, _)| i) - .unwrap_or_else(|| panic!("Target stage does not exist: {target:?}.")); - - self.stage_order.insert(target_index, label); - let prev = self.stages.insert(label, Box::new(stage)); - assert!(prev.is_none(), "Stage already exists: {label:?}."); - self - } - - /// Adds the given `system` to the stage identified by `stage_label`. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # fn my_system() {} - /// # let mut schedule = Schedule::default(); - /// # #[derive(StageLabel)] - /// # struct MyStage; - /// # schedule.add_stage(MyStage, SystemStage::parallel()); - /// # - /// schedule.add_system_to_stage(MyStage, my_system); - /// ``` - pub fn add_system_to_stage( - &mut self, - stage_label: impl StageLabel, - system: impl IntoSystemDescriptor, - ) -> &mut Self { - // Use a function instead of a closure to ensure that it is codegened inside bevy_ecs instead - // of the game. Closures inherit generic parameters from their enclosing function. - #[cold] - fn stage_not_found(stage_label: &dyn Debug) -> ! { - panic!("Stage '{stage_label:?}' does not exist or is not a SystemStage",) - } - - let label = stage_label.as_label(); - let stage = self - .get_stage_mut::(label) - .unwrap_or_else(move || stage_not_found(&label)); - stage.add_system(system); - self - } - - /// Adds the given `system_set` to the stage identified by `stage_label`. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # fn my_system() {} - /// # let mut schedule = Schedule::default(); - /// # #[derive(StageLabel)] - /// # struct MyStage; - /// # schedule.add_stage(MyStage, SystemStage::parallel()); - /// # - /// schedule.add_system_set_to_stage( - /// MyStage, - /// SystemSet::new() - /// .with_system(system_a) - /// .with_system(system_b) - /// .with_system(system_c) - /// ); - /// # - /// # fn system_a() {} - /// # fn system_b() {} - /// # fn system_c() {} - /// ``` - pub fn add_system_set_to_stage( - &mut self, - stage_label: impl StageLabel, - system_set: SystemSet, - ) -> &mut Self { - self.stage(stage_label, |stage: &mut SystemStage| { - stage.add_system_set(system_set) - }) - } - - /// Fetches the [`Stage`] of type `T` marked with `label`, then executes the provided - /// `func` passing the fetched stage to it as an argument. - /// - /// The `func` argument should be a function or a closure that accepts a mutable reference - /// to a struct implementing `Stage` and returns the same type. That means that it should - /// also assume that the stage has already been fetched successfully. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut schedule = Schedule::default(); - /// # #[derive(StageLabel)] - /// # struct MyStage; - /// # schedule.add_stage(MyStage, SystemStage::parallel()); - /// # - /// schedule.stage(MyStage, |stage: &mut SystemStage| { - /// stage.add_system(my_system) - /// }); - /// # - /// # fn my_system() {} - /// ``` - /// - /// # Panics - /// - /// Panics if `label` refers to a non-existing stage, or if it's not of type `T`. - pub fn stage &mut T>( - &mut self, - stage_label: impl StageLabel, - func: F, - ) -> &mut Self { - let label = stage_label.as_label(); - let stage = self.get_stage_mut::(label).unwrap_or_else(move || { - panic!("stage '{label:?}' does not exist or is the wrong type",) - }); - func(stage); - self - } - - /// Returns a shared reference to the stage identified by `label`, if it exists. - /// - /// If the requested stage does not exist, `None` is returned instead. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # fn my_system() {} - /// # let mut schedule = Schedule::default(); - /// # #[derive(StageLabel)] - /// # struct MyStage; - /// # schedule.add_stage(MyStage, SystemStage::parallel()); - /// # - /// let stage = schedule.get_stage::(MyStage).unwrap(); - /// ``` - pub fn get_stage(&self, stage_label: impl StageLabel) -> Option<&T> { - let label = stage_label.as_label(); - self.stages - .get(&label) - .and_then(|stage| stage.downcast_ref::()) - } - - /// Returns a unique, mutable reference to the stage identified by `label`, if it exists. - /// - /// If the requested stage does not exist, `None` is returned instead. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # fn my_system() {} - /// # let mut schedule = Schedule::default(); - /// # #[derive(StageLabel)] - /// # struct MyStage; - /// # schedule.add_stage(MyStage, SystemStage::parallel()); - /// # - /// let stage = schedule.get_stage_mut::(MyStage).unwrap(); - /// ``` - pub fn get_stage_mut(&mut self, stage_label: impl StageLabel) -> Option<&mut T> { - let label = stage_label.as_label(); - self.stages - .get_mut(&label) - .and_then(|stage| stage.downcast_mut::()) - } - - /// Removes a [`Stage`] from the schedule. - pub fn remove_stage(&mut self, stage_label: impl StageLabel) -> Option> { - let label = stage_label.as_label(); - - let Some(index) = self.stage_order.iter().position(|x| *x == label) else { - return None; - }; - self.stage_order.remove(index); - self.stages.remove(&label) - } - - /// Executes each [`Stage`] contained in the schedule, one at a time. - pub fn run_once(&mut self, world: &mut World) { - for label in &self.stage_order { - #[cfg(feature = "trace")] - let _stage_span = bevy_utils::tracing::info_span!("stage", name = ?label).entered(); - let stage = self.stages.get_mut(label).unwrap(); - stage.run(world); - } - } - - /// Iterates over all of schedule's stages and their labels, in execution order. - pub fn iter_stages(&self) -> impl Iterator { - self.stage_order - .iter() - .map(move |&label| (label, &*self.stages[&label])) - } -} - -impl Stage for Schedule { - fn run(&mut self, world: &mut World) { - loop { - match self.run_criteria.should_run(world) { - ShouldRun::No => return, - ShouldRun::Yes => { - self.run_once(world); - return; - } - ShouldRun::YesAndCheckAgain => { - self.run_once(world); - } - ShouldRun::NoAndCheckAgain => { - panic!("`NoAndCheckAgain` would loop infinitely in this situation.") - } - } - } - } -} diff --git a/crates/bevy_ecs/src/schedule/run_criteria.rs b/crates/bevy_ecs/src/schedule/run_criteria.rs deleted file mode 100644 index 8bfd84bf04c22..0000000000000 --- a/crates/bevy_ecs/src/schedule/run_criteria.rs +++ /dev/null @@ -1,402 +0,0 @@ -use crate::{ - prelude::System, - schedule::{GraphNode, RunCriteriaLabel, RunCriteriaLabelId}, - system::{BoxedSystem, IntoSystem, Local}, - world::World, -}; -use core::fmt::Debug; -use std::borrow::Cow; - -/// Determines whether a system should be executed or not, and how many times it should be ran each -/// time the stage is executed. -/// -/// A stage will loop over its run criteria and systems until no more systems need to be executed -/// and no more run criteria need to be checked. -/// - Any systems with run criteria that returns [`Yes`] will be ran exactly one more time during -/// the stage's execution that tick. -/// - Any systems with run criteria that returns [`No`] are not ran for the rest of the stage's -/// execution that tick. -/// - Any systems with run criteria that returns [`YesAndCheckAgain`] will be ran during this -/// iteration of the loop. After all the systems that need to run are ran, that criteria will be -/// checked again. -/// - Any systems with run criteria that returns [`NoAndCheckAgain`] will not be ran during this -/// iteration of the loop. After all the systems that need to run are ran, that criteria will be -/// checked again. -/// -/// [`Yes`]: ShouldRun::Yes -/// [`No`]: ShouldRun::No -/// [`YesAndCheckAgain`]: ShouldRun::YesAndCheckAgain -/// [`NoAndCheckAgain`]: ShouldRun::NoAndCheckAgain -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ShouldRun { - /// Yes, the system should run one more time this tick. - Yes, - /// No, the system should not run for the rest of this tick. - No, - /// Yes, the system should run, and after all systems in this stage have run, the criteria - /// should be checked again. This will cause the stage to loop over the remaining systems and - /// criteria this tick until they no longer need to be checked. - YesAndCheckAgain, - /// No, the system should not run right now, but after all systems in this stage have run, the - /// criteria should be checked again. This will cause the stage to loop over the remaining - /// systems and criteria this tick until they no longer need to be checked. - NoAndCheckAgain, -} - -impl ShouldRun { - /// A run criterion which returns [`ShouldRun::Yes`] exactly once. - /// - /// This leads to the systems controlled by it only being - /// executed one time only. - pub fn once(mut ran: Local) -> ShouldRun { - if *ran { - ShouldRun::No - } else { - *ran = true; - ShouldRun::Yes - } - } -} - -impl From for ShouldRun { - fn from(value: bool) -> Self { - if value { - ShouldRun::Yes - } else { - ShouldRun::No - } - } -} - -#[derive(Debug, Default)] -pub(crate) struct BoxedRunCriteria { - criteria_system: Option>, - initialized: bool, -} - -impl BoxedRunCriteria { - pub(crate) fn set(&mut self, criteria_system: BoxedSystem<(), ShouldRun>) { - self.criteria_system = Some(criteria_system); - self.initialized = false; - } - - pub(crate) fn should_run(&mut self, world: &mut World) -> ShouldRun { - if let Some(ref mut run_criteria) = self.criteria_system { - if !self.initialized { - run_criteria.initialize(world); - self.initialized = true; - } - let should_run = run_criteria.run((), world); - run_criteria.apply_buffers(world); - should_run - } else { - ShouldRun::Yes - } - } -} - -#[derive(Debug)] -pub(crate) enum RunCriteriaInner { - Single(BoxedSystem<(), ShouldRun>), - Piped { - input: usize, - system: BoxedSystem, - }, -} - -#[derive(Debug)] -pub(crate) struct RunCriteriaContainer { - pub(crate) should_run: ShouldRun, - pub(crate) inner: RunCriteriaInner, - pub(crate) label: Option, - pub(crate) before: Vec, - pub(crate) after: Vec, -} - -impl RunCriteriaContainer { - pub(crate) fn from_descriptor(descriptor: RunCriteriaDescriptor) -> Self { - Self { - should_run: ShouldRun::Yes, - inner: match descriptor.system { - RunCriteriaSystem::Single(system) => RunCriteriaInner::Single(system), - RunCriteriaSystem::Piped(system) => RunCriteriaInner::Piped { input: 0, system }, - }, - label: descriptor.label, - before: descriptor.before, - after: descriptor.after, - } - } - - pub(crate) fn name(&self) -> Cow<'static, str> { - match &self.inner { - RunCriteriaInner::Single(system) => system.name(), - RunCriteriaInner::Piped { system, .. } => system.name(), - } - } - - pub(crate) fn initialize(&mut self, world: &mut World) { - match &mut self.inner { - RunCriteriaInner::Single(system) => system.initialize(world), - RunCriteriaInner::Piped { system, .. } => system.initialize(world), - } - } -} - -impl GraphNode for RunCriteriaContainer { - type Label = RunCriteriaLabelId; - - fn name(&self) -> Cow<'static, str> { - match &self.inner { - RunCriteriaInner::Single(system) => system.name(), - RunCriteriaInner::Piped { system, .. } => system.name(), - } - } - - fn labels(&self) -> &[RunCriteriaLabelId] { - if let Some(ref label) = self.label { - std::slice::from_ref(label) - } else { - &[] - } - } - - fn before(&self) -> &[RunCriteriaLabelId] { - &self.before - } - - fn after(&self) -> &[RunCriteriaLabelId] { - &self.after - } -} - -#[derive(Debug)] -pub enum RunCriteriaDescriptorOrLabel { - Descriptor(RunCriteriaDescriptor), - Label(RunCriteriaLabelId), -} - -#[derive(Debug, Clone, Copy)] -pub(crate) enum DuplicateLabelStrategy { - Panic, - Discard, -} - -#[derive(Debug)] -pub struct RunCriteriaDescriptor { - pub(crate) system: RunCriteriaSystem, - pub(crate) label: Option, - pub(crate) duplicate_label_strategy: DuplicateLabelStrategy, - pub(crate) before: Vec, - pub(crate) after: Vec, -} - -#[derive(Debug)] -pub(crate) enum RunCriteriaSystem { - Single(BoxedSystem<(), ShouldRun>), - Piped(BoxedSystem), -} - -pub trait IntoRunCriteria { - fn into(self) -> RunCriteriaDescriptorOrLabel; -} - -impl IntoRunCriteria for RunCriteriaDescriptorOrLabel { - fn into(self) -> RunCriteriaDescriptorOrLabel { - self - } -} - -impl IntoRunCriteria for RunCriteriaDescriptor { - fn into(self) -> RunCriteriaDescriptorOrLabel { - RunCriteriaDescriptorOrLabel::Descriptor(self) - } -} - -impl IntoRunCriteria> for BoxedSystem<(), ShouldRun> { - fn into(self) -> RunCriteriaDescriptorOrLabel { - RunCriteriaDescriptorOrLabel::Descriptor(new_run_criteria_descriptor(self)) - } -} - -impl IntoRunCriteria<(BoxedSystem<(), ShouldRun>, Param)> for S -where - S: IntoSystem<(), ShouldRun, Param>, -{ - fn into(self) -> RunCriteriaDescriptorOrLabel { - RunCriteriaDescriptorOrLabel::Descriptor(new_run_criteria_descriptor(Box::new( - IntoSystem::into_system(self), - ))) - } -} - -impl IntoRunCriteria for L -where - L: RunCriteriaLabel, -{ - fn into(self) -> RunCriteriaDescriptorOrLabel { - RunCriteriaDescriptorOrLabel::Label(self.as_label()) - } -} - -impl IntoRunCriteria for RunCriteria { - fn into(self) -> RunCriteriaDescriptorOrLabel { - RunCriteriaDescriptorOrLabel::Label(self.label) - } -} - -pub trait RunCriteriaDescriptorCoercion { - /// Assigns a label to the criteria. Must be unique. - fn label(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor; - - /// Assigns a label to the criteria. If the given label is already in use, - /// this criteria will be discarded before initialization. - fn label_discard_if_duplicate(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor; - - /// Specifies that this criteria must be evaluated before a criteria with the given label. - fn before(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor; - - /// Specifies that this criteria must be evaluated after a criteria with the given label. - fn after(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor; -} - -impl RunCriteriaDescriptorCoercion<()> for RunCriteriaDescriptor { - fn label(mut self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor { - self.label = Some(label.as_label()); - self.duplicate_label_strategy = DuplicateLabelStrategy::Panic; - self - } - - fn label_discard_if_duplicate(mut self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor { - self.label = Some(label.as_label()); - self.duplicate_label_strategy = DuplicateLabelStrategy::Discard; - self - } - - fn before(mut self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor { - self.before.push(label.as_label()); - self - } - - fn after(mut self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor { - self.after.push(label.as_label()); - self - } -} - -fn new_run_criteria_descriptor(system: BoxedSystem<(), ShouldRun>) -> RunCriteriaDescriptor { - RunCriteriaDescriptor { - system: RunCriteriaSystem::Single(system), - label: None, - duplicate_label_strategy: DuplicateLabelStrategy::Panic, - before: vec![], - after: vec![], - } -} - -impl RunCriteriaDescriptorCoercion<()> for BoxedSystem<(), ShouldRun> { - fn label(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor { - new_run_criteria_descriptor(self).label(label) - } - - fn label_discard_if_duplicate(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor { - new_run_criteria_descriptor(self).label_discard_if_duplicate(label) - } - - fn before(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor { - new_run_criteria_descriptor(self).before(label) - } - - fn after(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor { - new_run_criteria_descriptor(self).after(label) - } -} - -impl RunCriteriaDescriptorCoercion for S -where - S: IntoSystem<(), ShouldRun, Param>, -{ - fn label(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor { - new_run_criteria_descriptor(Box::new(IntoSystem::into_system(self))).label(label) - } - - fn label_discard_if_duplicate(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor { - new_run_criteria_descriptor(Box::new(IntoSystem::into_system(self))) - .label_discard_if_duplicate(label) - } - - fn before(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor { - new_run_criteria_descriptor(Box::new(IntoSystem::into_system(self))).before(label) - } - - fn after(self, label: impl RunCriteriaLabel) -> RunCriteriaDescriptor { - new_run_criteria_descriptor(Box::new(IntoSystem::into_system(self))).after(label) - } -} - -#[derive(Debug)] -pub struct RunCriteria { - label: RunCriteriaLabelId, -} - -impl RunCriteria { - /// Constructs a new run criteria that will retrieve the result of the criteria `label` - /// and pipe it as input to `system`. - pub fn pipe

( - label: impl RunCriteriaLabel, - system: impl IntoSystem, - ) -> RunCriteriaDescriptor { - RunCriteriaDescriptor { - system: RunCriteriaSystem::Piped(Box::new(IntoSystem::into_system(system))), - label: None, - duplicate_label_strategy: DuplicateLabelStrategy::Panic, - before: vec![], - after: vec![label.as_label()], - } - } -} - -impl Debug for dyn System + 'static { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "System {} with In=(), Out=ShouldRun: {{{}}}", - self.name(), - { - if self.is_send() { - if self.is_exclusive() { - "is_send is_exclusive" - } else { - "is_send" - } - } else if self.is_exclusive() { - "is_exclusive" - } else { - "" - } - }, - ) - } -} - -impl Debug for dyn System + 'static { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "System {} with In=ShouldRun, Out=ShouldRun: {{{}}}", - self.name(), - { - if self.is_send() { - if self.is_exclusive() { - "is_send is_exclusive" - } else { - "is_send" - } - } else if self.is_exclusive() { - "is_exclusive" - } else { - "" - } - }, - ) - } -} diff --git a/crates/bevy_ecs/src/schedule/stage.rs b/crates/bevy_ecs/src/schedule/stage.rs deleted file mode 100644 index 05846c630bb7c..0000000000000 --- a/crates/bevy_ecs/src/schedule/stage.rs +++ /dev/null @@ -1,1614 +0,0 @@ -use crate::{ - self as bevy_ecs, - change_detection::CHECK_TICK_THRESHOLD, - component::ComponentId, - prelude::IntoSystem, - schedule::{ - graph_utils::{self, DependencyGraphError}, - BoxedRunCriteria, DuplicateLabelStrategy, ExclusiveInsertionPoint, GraphNode, - ParallelExecutor, ParallelSystemExecutor, RunCriteriaContainer, RunCriteriaDescriptor, - RunCriteriaDescriptorOrLabel, RunCriteriaInner, RunCriteriaLabelId, ShouldRun, - SingleThreadedExecutor, SystemContainer, SystemDescriptor, SystemLabelId, SystemSet, - }, - world::{World, WorldId}, -}; -use bevy_ecs_macros::Resource; -use bevy_utils::{tracing::warn, HashMap, HashSet}; -use core::fmt::Debug; -use downcast_rs::{impl_downcast, Downcast}; - -use super::{IntoSystemDescriptor, Schedule}; - -/// A type that can run as a step of a [`Schedule`](super::Schedule). -pub trait Stage: Downcast + Send + Sync { - /// Runs the stage; this happens once per update. - /// Implementors must initialize all of their state and systems before running the first time. - fn run(&mut self, world: &mut World); -} - -impl Debug for dyn Stage { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(as_systemstage) = self.as_any().downcast_ref::() { - write!(f, "{as_systemstage:?}") - } else if let Some(as_schedule) = self.as_any().downcast_ref::() { - write!(f, "{as_schedule:?}") - } else { - write!(f, "Unknown dyn Stage") - } - } -} - -impl_downcast!(Stage); - -/// When this resource is present in the `App`'s `Resources`, -/// each `SystemStage` will log a report containing -/// pairs of systems with ambiguous execution order. -/// -/// Systems that access the same Component or Resource within the same stage -/// risk an ambiguous order that could result in logic bugs, unless they have an -/// explicit execution ordering constraint between them. -/// -/// This occurs because, in the absence of explicit constraints, systems are executed in -/// an unstable, arbitrary order within each stage that may vary between runs and frames. -/// -/// Some ambiguities reported by the ambiguity checker may be warranted (to allow two systems to run -/// without blocking each other) or spurious, as the exact combination of archetypes used may -/// prevent them from ever conflicting during actual gameplay. You can resolve the warnings produced -/// by the ambiguity checker by adding `.before` or `.after` to one of the conflicting systems -/// referencing the other system to force a specific ordering. -/// -/// The checker may report a system more times than the amount of constraints it would actually need -/// to have unambiguous order with regards to a group of already-constrained systems. -#[derive(Resource, Default)] -pub struct ReportExecutionOrderAmbiguities; - -/// Stores and executes systems. Execution order is not defined unless explicitly specified; -/// see `SystemDescriptor` documentation. -pub struct SystemStage { - /// The WorldId this stage was last run on. - world_id: Option, - /// Instance of a scheduling algorithm for running the systems. - executor: Box, - /// Determines whether the stage should run. - stage_run_criteria: BoxedRunCriteria, - /// Topologically sorted run criteria of systems. - run_criteria: Vec, - /// Topologically sorted exclusive systems that want to be run at the start of the stage. - pub(super) exclusive_at_start: Vec, - /// Topologically sorted exclusive systems that want to be run after parallel systems but - /// before the application of their command buffers. - pub(super) exclusive_before_commands: Vec, - /// Topologically sorted exclusive systems that want to be run at the end of the stage. - pub(super) exclusive_at_end: Vec, - /// Topologically sorted parallel systems. - pub(super) parallel: Vec, - /// Determines if the stage was modified and needs to rebuild its graphs and orders. - pub(super) systems_modified: bool, - /// Determines if the stage's executor was changed. - executor_modified: bool, - /// Newly inserted run criteria that will be initialized at the next opportunity. - uninitialized_run_criteria: Vec<(usize, DuplicateLabelStrategy)>, - /// Newly inserted systems that will be initialized at the next opportunity. - uninitialized_at_start: Vec, - /// Newly inserted systems that will be initialized at the next opportunity. - uninitialized_before_commands: Vec, - /// Newly inserted systems that will be initialized at the next opportunity. - uninitialized_at_end: Vec, - /// Newly inserted systems that will be initialized at the next opportunity. - uninitialized_parallel: Vec, - /// Saves the value of the World change_tick during the last tick check - last_tick_check: u32, - /// If true, buffers will be automatically applied at the end of the stage. If false, buffers must be manually applied. - apply_buffers: bool, - must_read_resource: Option, -} - -impl SystemStage { - pub fn new(executor: Box) -> Self { - SystemStage { - world_id: None, - executor, - stage_run_criteria: Default::default(), - run_criteria: vec![], - uninitialized_run_criteria: vec![], - exclusive_at_start: Default::default(), - exclusive_before_commands: Default::default(), - exclusive_at_end: Default::default(), - parallel: vec![], - systems_modified: true, - executor_modified: true, - uninitialized_parallel: vec![], - uninitialized_at_start: vec![], - uninitialized_before_commands: vec![], - uninitialized_at_end: vec![], - last_tick_check: Default::default(), - apply_buffers: true, - must_read_resource: None, - } - } - - pub fn single(system: impl IntoSystemDescriptor) -> Self { - Self::single_threaded().with_system(system) - } - - pub fn single_threaded() -> Self { - Self::new(Box::::default()) - } - - pub fn parallel() -> Self { - Self::new(Box::::default()) - } - - pub fn get_executor(&self) -> Option<&T> { - self.executor.downcast_ref() - } - - pub fn get_executor_mut(&mut self) -> Option<&mut T> { - self.executor_modified = true; - self.executor.downcast_mut() - } - - pub fn set_executor(&mut self, executor: Box) { - self.executor_modified = true; - self.executor = executor; - } - - pub fn set_must_read_resource(&mut self, resource_id: ComponentId) { - self.must_read_resource = Some(resource_id); - } - - #[must_use] - pub fn with_system(mut self, system: impl IntoSystemDescriptor) -> Self { - self.add_system(system); - self - } - - pub fn add_system(&mut self, system: impl IntoSystemDescriptor) -> &mut Self { - self.add_system_inner(system.into_descriptor(), None); - self - } - - fn add_system_inner( - &mut self, - mut descriptor: SystemDescriptor, - default_run_criteria: Option, - ) { - self.systems_modified = true; - if let Some(insertion_point) = descriptor.exclusive_insertion_point { - let criteria = descriptor.run_criteria.take(); - let mut container = SystemContainer::from_descriptor(descriptor); - match criteria { - Some(RunCriteriaDescriptorOrLabel::Label(label)) => { - container.run_criteria_label = Some(label); - } - Some(RunCriteriaDescriptorOrLabel::Descriptor(criteria_descriptor)) => { - container.run_criteria_label = criteria_descriptor.label; - container.run_criteria_index = - Some(self.add_run_criteria_internal(criteria_descriptor)); - } - None => { - container.run_criteria_index = default_run_criteria; - } - } - match insertion_point { - ExclusiveInsertionPoint::AtStart => { - let index = self.exclusive_at_start.len(); - self.uninitialized_at_start.push(index); - self.exclusive_at_start.push(container); - } - ExclusiveInsertionPoint::BeforeCommands => { - let index = self.exclusive_before_commands.len(); - self.uninitialized_before_commands.push(index); - self.exclusive_before_commands.push(container); - } - ExclusiveInsertionPoint::AtEnd => { - let index = self.exclusive_at_end.len(); - self.uninitialized_at_end.push(index); - self.exclusive_at_end.push(container); - } - } - } else { - let criteria = descriptor.run_criteria.take(); - let mut container = SystemContainer::from_descriptor(descriptor); - match criteria { - Some(RunCriteriaDescriptorOrLabel::Label(label)) => { - container.run_criteria_label = Some(label); - } - Some(RunCriteriaDescriptorOrLabel::Descriptor(criteria_descriptor)) => { - container.run_criteria_label = criteria_descriptor.label; - container.run_criteria_index = - Some(self.add_run_criteria_internal(criteria_descriptor)); - } - None => { - container.run_criteria_index = default_run_criteria; - } - } - self.uninitialized_parallel.push(self.parallel.len()); - self.parallel.push(container); - } - } - - pub fn apply_buffers(&mut self, world: &mut World) { - #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!("stage::apply_buffers").entered(); - for container in &mut self.parallel { - container.system_mut().apply_buffers(world); - } - } - - pub fn set_apply_buffers(&mut self, apply_buffers: bool) { - self.apply_buffers = apply_buffers; - } - - /// Topologically sorted parallel systems. - /// - /// Note that systems won't be fully-formed until the stage has been run at least once. - pub fn parallel_systems(&self) -> &[SystemContainer] { - &self.parallel - } - - /// Topologically sorted exclusive systems that want to be run at the start of the stage. - /// - /// Note that systems won't be fully-formed until the stage has been run at least once. - pub fn exclusive_at_start_systems(&self) -> &[SystemContainer] { - &self.exclusive_at_start - } - - /// Topologically sorted exclusive systems that want to be run at the end of the stage. - /// - /// Note that systems won't be fully-formed until the stage has been run at least once. - pub fn exclusive_at_end_systems(&self) -> &[SystemContainer] { - &self.exclusive_at_end - } - - /// Topologically sorted exclusive systems that want to be run after parallel systems but - /// before the application of their command buffers. - /// - /// Note that systems won't be fully-formed until the stage has been run at least once. - pub fn exclusive_before_commands_systems(&self) -> &[SystemContainer] { - &self.exclusive_before_commands - } - - #[must_use] - pub fn with_system_set(mut self, system_set: SystemSet) -> Self { - self.add_system_set(system_set); - self - } - - pub fn add_system_set(&mut self, system_set: SystemSet) -> &mut Self { - self.systems_modified = true; - let (run_criteria, mut systems) = system_set.bake(); - let set_run_criteria_index = run_criteria.and_then(|criteria| { - // validate that no systems have criteria - for descriptor in &mut systems { - if let Some(name) = descriptor - .run_criteria - .is_some() - .then(|| descriptor.system.name()) - { - panic!( - "The system {name} has a run criteria, but its `SystemSet` also has a run \ - criteria. This is not supported. Consider moving the system into a \ - different `SystemSet` or calling `add_system()` instead." - ) - } - } - match criteria { - RunCriteriaDescriptorOrLabel::Descriptor(descriptor) => { - Some(self.add_run_criteria_internal(descriptor)) - } - RunCriteriaDescriptorOrLabel::Label(label) => { - for system in &mut systems { - system.run_criteria = Some(RunCriteriaDescriptorOrLabel::Label(label)); - } - - None - } - } - }); - for system in systems { - self.add_system_inner(system, set_run_criteria_index); - } - self - } - - #[must_use] - pub fn with_run_criteria>( - mut self, - system: S, - ) -> Self { - self.set_run_criteria(system); - self - } - - pub fn set_run_criteria>( - &mut self, - system: S, - ) -> &mut Self { - self.stage_run_criteria - .set(Box::new(IntoSystem::into_system(system))); - self - } - - #[must_use] - pub fn with_system_run_criteria(mut self, run_criteria: RunCriteriaDescriptor) -> Self { - self.add_system_run_criteria(run_criteria); - self - } - - pub fn add_system_run_criteria(&mut self, run_criteria: RunCriteriaDescriptor) -> &mut Self { - self.add_run_criteria_internal(run_criteria); - self - } - - pub(crate) fn add_run_criteria_internal(&mut self, descriptor: RunCriteriaDescriptor) -> usize { - let index = self.run_criteria.len(); - self.uninitialized_run_criteria - .push((index, descriptor.duplicate_label_strategy)); - - self.run_criteria - .push(RunCriteriaContainer::from_descriptor(descriptor)); - index - } - - fn initialize_systems(&mut self, world: &mut World) { - let mut criteria_labels = HashMap::default(); - let uninitialized_criteria: HashMap<_, _> = - self.uninitialized_run_criteria.drain(..).collect(); - // track the number of filtered criteria to correct run criteria indices - let mut filtered_criteria = 0; - let mut new_indices = Vec::new(); - self.run_criteria = self - .run_criteria - .drain(..) - .enumerate() - .filter_map(|(index, mut container)| { - let new_index = index - filtered_criteria; - let label = container.label; - if let Some(strategy) = uninitialized_criteria.get(&index) { - if let Some(ref label) = label { - if let Some(duplicate_index) = criteria_labels.get(label) { - match strategy { - DuplicateLabelStrategy::Panic => panic!( - "Run criteria {} is labelled with {:?}, which \ - is already in use. Consider using \ - `RunCriteriaDescriptorCoercion::label_discard_if_duplicate().", - container.name(), - container.label - ), - DuplicateLabelStrategy::Discard => { - new_indices.push(*duplicate_index); - filtered_criteria += 1; - return None; - } - } - } - } - container.initialize(world); - } - if let Some(label) = label { - criteria_labels.insert(label, new_index); - } - new_indices.push(new_index); - Some(container) - }) - .collect(); - - for index in self.uninitialized_at_start.drain(..) { - let container = &mut self.exclusive_at_start[index]; - if let Some(index) = container.run_criteria() { - container.set_run_criteria(new_indices[index]); - } - container.system_mut().initialize(world); - } - for index in self.uninitialized_before_commands.drain(..) { - let container = &mut self.exclusive_before_commands[index]; - if let Some(index) = container.run_criteria() { - container.set_run_criteria(new_indices[index]); - } - container.system_mut().initialize(world); - } - for index in self.uninitialized_at_end.drain(..) { - let container = &mut self.exclusive_at_end[index]; - if let Some(index) = container.run_criteria() { - container.set_run_criteria(new_indices[index]); - } - container.system_mut().initialize(world); - } - for index in self.uninitialized_parallel.drain(..) { - let container = &mut self.parallel[index]; - if let Some(index) = container.run_criteria() { - container.set_run_criteria(new_indices[index]); - } - container.system_mut().initialize(world); - } - } - - /// Rearranges all systems in topological orders. Systems must be initialized. - fn rebuild_orders_and_dependencies(&mut self) { - // This assertion exists to document that the number of systems in a stage is limited - // to guarantee that change detection never yields false positives. However, it's possible - // (but still unlikely) to circumvent this by abusing exclusive or chained systems. - assert!( - self.exclusive_at_start.len() - + self.exclusive_before_commands.len() - + self.exclusive_at_end.len() - + self.parallel.len() - < (CHECK_TICK_THRESHOLD as usize) - ); - debug_assert!( - self.uninitialized_run_criteria.is_empty() - && self.uninitialized_parallel.is_empty() - && self.uninitialized_at_start.is_empty() - && self.uninitialized_before_commands.is_empty() - && self.uninitialized_at_end.is_empty() - ); - fn unwrap_dependency_cycle_error( - result: Result>, - nodes: &[Node], - nodes_description: &'static str, - ) -> Output { - match result { - Ok(output) => output, - Err(DependencyGraphError::GraphCycles(cycle)) => { - use std::fmt::Write; - let mut message = format!("Found a dependency cycle in {nodes_description}:"); - writeln!(message).unwrap(); - for (index, labels) in &cycle { - writeln!(message, " - {}", nodes[*index].name()).unwrap(); - writeln!( - message, - " wants to be after (because of labels: {labels:?})", - ) - .unwrap(); - } - writeln!(message, " - {}", cycle[0].0).unwrap(); - panic!("{}", message); - } - } - } - let run_criteria_labels = unwrap_dependency_cycle_error( - self.process_run_criteria(), - &self.run_criteria, - "run criteria", - ); - unwrap_dependency_cycle_error( - process_systems(&mut self.parallel, &run_criteria_labels), - &self.parallel, - "parallel systems", - ); - unwrap_dependency_cycle_error( - process_systems(&mut self.exclusive_at_start, &run_criteria_labels), - &self.exclusive_at_start, - "exclusive systems at start of stage", - ); - unwrap_dependency_cycle_error( - process_systems(&mut self.exclusive_before_commands, &run_criteria_labels), - &self.exclusive_before_commands, - "exclusive systems before commands of stage", - ); - unwrap_dependency_cycle_error( - process_systems(&mut self.exclusive_at_end, &run_criteria_labels), - &self.exclusive_at_end, - "exclusive systems at end of stage", - ); - } - - fn check_uses_resource(&self, resource_id: ComponentId, world: &World) { - debug_assert!(!self.systems_modified); - for system in &self.parallel { - if !system.component_access().has_read(resource_id) { - let component_name = world.components().get_info(resource_id).unwrap().name(); - warn!( - "System {} doesn't access resource {component_name}, despite being required to", - system.name() - ); - } - } - } - - /// All system and component change ticks are scanned once the world counter has incremented - /// at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD) - /// times since the previous `check_tick` scan. - /// - /// During each scan, any change ticks older than [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE) - /// are clamped to that age. This prevents false positives from appearing due to overflow. - fn check_change_ticks(&mut self, world: &mut World) { - let change_tick = world.change_tick(); - let ticks_since_last_check = change_tick.wrapping_sub(self.last_tick_check); - - if ticks_since_last_check >= CHECK_TICK_THRESHOLD { - // Check all system change ticks. - for exclusive_system in &mut self.exclusive_at_start { - exclusive_system.system_mut().check_change_tick(change_tick); - } - for exclusive_system in &mut self.exclusive_before_commands { - exclusive_system.system_mut().check_change_tick(change_tick); - } - for exclusive_system in &mut self.exclusive_at_end { - exclusive_system.system_mut().check_change_tick(change_tick); - } - for parallel_system in &mut self.parallel { - parallel_system.system_mut().check_change_tick(change_tick); - } - - // Check all component change ticks. - world.check_change_ticks(); - self.last_tick_check = change_tick; - } - } - - /// Sorts run criteria and populates resolved input-criteria for piping. - /// Returns a map of run criteria labels to their indices. - fn process_run_criteria( - &mut self, - ) -> Result, DependencyGraphError>> - { - let graph = graph_utils::build_dependency_graph(&self.run_criteria); - let order = graph_utils::topological_order(&graph)?; - let mut order_inverted = order.iter().enumerate().collect::>(); - order_inverted.sort_unstable_by_key(|(_, &key)| key); - let labels: HashMap<_, _> = self - .run_criteria - .iter() - .enumerate() - .filter_map(|(index, criteria)| { - criteria - .label - .as_ref() - .map(|&label| (label, order_inverted[index].0)) - }) - .collect(); - for criteria in &mut self.run_criteria { - if let RunCriteriaInner::Piped { input: parent, .. } = &mut criteria.inner { - let label = &criteria.after[0]; - *parent = *labels.get(label).unwrap_or_else(|| { - panic!("Couldn't find run criteria labelled {label:?} to pipe from.",) - }); - } - } - - fn update_run_criteria_indices( - systems: &mut [SystemContainer], - order_inverted: &[(usize, &usize)], - ) { - for system in systems { - if let Some(index) = system.run_criteria() { - system.set_run_criteria(order_inverted[index].0); - } - } - } - - update_run_criteria_indices(&mut self.exclusive_at_end, &order_inverted); - update_run_criteria_indices(&mut self.exclusive_at_start, &order_inverted); - update_run_criteria_indices(&mut self.exclusive_before_commands, &order_inverted); - update_run_criteria_indices(&mut self.parallel, &order_inverted); - - let mut temp = self.run_criteria.drain(..).map(Some).collect::>(); - for index in order { - self.run_criteria.push(temp[index].take().unwrap()); - } - Ok(labels) - } - - pub fn vec_system_container_debug( - &self, - name: &str, - v: &Vec, - f: &mut std::fmt::Formatter<'_>, - ) -> std::fmt::Result { - write!(f, "{name}: ")?; - if v.len() > 1 { - writeln!(f, "[")?; - for sc in v.iter() { - writeln!(f, "{sc:?},")?; - } - write!(f, "], ") - } else { - write!(f, "{v:?}, ") - } - } -} - -impl std::fmt::Debug for SystemStage { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "SystemStage: {{ ")?; - write!( - f, - "world_id: {:?}, executor: {:?}, stage_run_criteria: {:?}, run_criteria: {:?}, ", - self.world_id, self.executor, self.stage_run_criteria, self.run_criteria - )?; - self.vec_system_container_debug("exclusive_at_start", &self.exclusive_at_start, f)?; - self.vec_system_container_debug( - "exclusive_before_commands", - &self.exclusive_before_commands, - f, - )?; - self.vec_system_container_debug("exclusive_at_end", &self.exclusive_at_end, f)?; - self.vec_system_container_debug("parallel", &self.parallel, f)?; - write!( - f, - "systems_modified: {:?}, uninitialized_run_criteria: {:?}, ", - self.systems_modified, self.uninitialized_run_criteria - )?; - write!( - f, - "uninitialized_at_start: {:?}, uninitialized_before_commands: {:?}, ", - self.uninitialized_at_start, self.uninitialized_before_commands - )?; - write!( - f, - "uninitialized_at_end: {:?}, uninitialized_parallel: {:?}, ", - self.uninitialized_at_end, self.uninitialized_parallel - )?; - write!( - f, - "last_tick_check: {:?}, apply_buffers: {:?}, ", - self.last_tick_check, self.apply_buffers - )?; - write!(f, "must_read_resource: {:?}}}", self.must_read_resource) - } -} - -/// Sorts given system containers topologically, populates their resolved dependencies -/// and run criteria. -fn process_systems( - systems: &mut Vec, - run_criteria_labels: &HashMap, -) -> Result<(), DependencyGraphError>> { - let mut graph = graph_utils::build_dependency_graph(systems); - let order = graph_utils::topological_order(&graph)?; - let mut order_inverted = order.iter().enumerate().collect::>(); - order_inverted.sort_unstable_by_key(|(_, &key)| key); - for (index, container) in systems.iter_mut().enumerate() { - if let Some(index) = container.run_criteria_label().map(|label| { - *run_criteria_labels - .get(label) - .unwrap_or_else(|| panic!("No run criteria with label {label:?} found.")) - }) { - container.set_run_criteria(index); - } - container.set_dependencies( - graph - .get_mut(&index) - .unwrap() - .drain() - .map(|(index, _)| order_inverted[index].0), - ); - } - let mut temp = systems.drain(..).map(Some).collect::>(); - for index in order { - systems.push(temp[index].take().unwrap()); - } - Ok(()) -} - -impl Stage for SystemStage { - fn run(&mut self, world: &mut World) { - if let Some(world_id) = self.world_id { - assert!( - world.id() == world_id, - "Cannot run SystemStage on two different Worlds" - ); - } else { - self.world_id = Some(world.id()); - } - - if self.systems_modified { - self.initialize_systems(world); - self.rebuild_orders_and_dependencies(); - self.systems_modified = false; - self.executor.rebuild_cached_data(&self.parallel); - self.executor_modified = false; - if world.contains_resource::() { - self.report_ambiguities(world); - } - if let Some(resource_id) = self.must_read_resource { - self.check_uses_resource(resource_id, world); - } - } else if self.executor_modified { - self.executor.rebuild_cached_data(&self.parallel); - self.executor_modified = false; - } - - let mut run_stage_loop = true; - while run_stage_loop { - let should_run = self.stage_run_criteria.should_run(world); - match should_run { - ShouldRun::No => return, - ShouldRun::NoAndCheckAgain => continue, - ShouldRun::YesAndCheckAgain => (), - ShouldRun::Yes => { - run_stage_loop = false; - } - }; - - // Evaluate system run criteria. - for index in 0..self.run_criteria.len() { - let (run_criteria, tail) = self.run_criteria.split_at_mut(index); - let mut criteria = &mut tail[0]; - - #[cfg(feature = "trace")] - let _span = - bevy_utils::tracing::info_span!("run criteria", name = &*criteria.name()) - .entered(); - - 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), - } - } - - let mut run_system_loop = true; - let mut default_should_run = ShouldRun::Yes; - while run_system_loop { - run_system_loop = false; - - fn should_run( - container: &SystemContainer, - run_criteria: &[RunCriteriaContainer], - default: ShouldRun, - ) -> bool { - matches!( - container - .run_criteria() - .map(|index| run_criteria[index].should_run) - .unwrap_or(default), - ShouldRun::Yes | ShouldRun::YesAndCheckAgain - ) - } - - // 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, default_should_run) { - { - #[cfg(feature = "trace")] - let _system_span = bevy_utils::tracing::info_span!( - "exclusive_system", - name = &*container.name() - ) - .entered(); - container.system_mut().run((), world); - } - container.system_mut().apply_buffers(world); - } - } - - // Run parallel systems using the executor. - // TODO: hard dependencies, nested sets, whatever... should be evaluated here. - for container in &mut self.parallel { - container.should_run = - should_run(container, &self.run_criteria, default_should_run); - } - self.executor.run_systems(&mut self.parallel, world); - - // Run systems that want to be between parallel systems and their command buffers. - for container in &mut self.exclusive_before_commands { - if should_run(container, &self.run_criteria, default_should_run) { - { - #[cfg(feature = "trace")] - let _system_span = bevy_utils::tracing::info_span!( - "exclusive_system", - name = &*container.name() - ) - .entered(); - container.system_mut().run((), world); - } - container.system_mut().apply_buffers(world); - } - } - - // Apply parallel systems' buffers. - if self.apply_buffers { - for container in &mut self.parallel { - if container.should_run { - container.system_mut().apply_buffers(world); - } - } - } - - // Run systems that want to be at the end of stage. - for container in &mut self.exclusive_at_end { - if should_run(container, &self.run_criteria, default_should_run) { - { - #[cfg(feature = "trace")] - let _system_span = bevy_utils::tracing::info_span!( - "exclusive_system", - name = &*container.name() - ) - .entered(); - container.system_mut().run((), world); - } - container.system_mut().apply_buffers(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); - } - } - match criteria.should_run { - ShouldRun::Yes - | ShouldRun::YesAndCheckAgain - | ShouldRun::NoAndCheckAgain => { - run_system_loop = true; - } - ShouldRun::No => (), - } - } - } - } - - // after the first loop, default to not running systems without run criteria - default_should_run = ShouldRun::No; - } - } - } -} - -#[cfg(test)] -mod tests { - use bevy_ecs_macros::RunCriteriaLabel; - - use crate::{ - schedule::{ - IntoSystemDescriptor, RunCriteria, RunCriteriaDescriptorCoercion, ShouldRun, - SingleThreadedExecutor, Stage, SystemLabel, SystemSet, SystemStage, - }, - system::{In, Local, Query, ResMut}, - world::World, - }; - - use crate as bevy_ecs; - use crate::component::Component; - use crate::system::Resource; - - #[derive(Component)] - struct W(T); - #[derive(Resource)] - struct R(usize); - - #[derive(Resource, Default)] - struct EntityCount(Vec); - - fn make_exclusive(tag: usize) -> impl FnMut(&mut World) { - move |world| world.resource_mut::().0.push(tag) - } - - fn make_parallel(tag: usize) -> impl FnMut(ResMut) { - move |mut resource: ResMut| resource.0.push(tag) - } - - fn every_other_time(mut has_ran: Local) -> ShouldRun { - *has_ran = !*has_ran; - if *has_ran { - ShouldRun::Yes - } else { - ShouldRun::No - } - } - - #[test] - fn insertion_points() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(0).at_start()) - .with_system(make_parallel(1)) - .with_system(make_exclusive(2).before_commands()) - .with_system(make_exclusive(3).at_end()); - stage.run(&mut world); - assert_eq!(world.resource_mut::().0, vec![0, 1, 2, 3]); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 0, 1, 2, 3] - ); - - world.resource_mut::().0.clear(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(2).before_commands()) - .with_system(make_exclusive(3).at_end()) - .with_system(make_parallel(1)) - .with_system(make_exclusive(0).at_start()); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 2, 3]); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 0, 1, 2, 3] - ); - - world.resource_mut::().0.clear(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(2).before_commands()) - .with_system(make_parallel(3).at_end()) - .with_system(make_parallel(1)) - .with_system(make_parallel(0).at_start()); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 2, 3]); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 0, 1, 2, 3] - ); - } - - #[derive(SystemLabel)] - enum TestLabels { - L0, - L1, - L2, - L3, - L4, - First, - L01, - L234, - } - use TestLabels::*; - - #[test] - fn exclusive_after() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(1).label(L1).after(L0)) - .with_system(make_exclusive(2).after(L1)) - .with_system(make_exclusive(0).label(L0)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 2, 0, 1, 2]); - } - - #[test] - fn exclusive_before() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(1).label(L1).before(L2)) - .with_system(make_exclusive(2).label(L2)) - .with_system(make_exclusive(0).before(L1)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 2, 0, 1, 2]); - } - - #[test] - fn exclusive_mixed() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(2).label(L2)) - .with_system(make_exclusive(1).after(L0).before(L2)) - .with_system(make_exclusive(0).label(L0)) - .with_system(make_exclusive(4).label(L4)) - .with_system(make_exclusive(3).after(L2).before(L4)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - } - - #[test] - fn exclusive_multiple_labels() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(1).label(First).after(L0)) - .with_system(make_exclusive(2).after(First)) - .with_system(make_exclusive(0).label(First).label(L0)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 2, 0, 1, 2]); - - world.resource_mut::().0.clear(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(2).after(L01).label(L2)) - .with_system(make_exclusive(1).label(L01).after(L0)) - .with_system(make_exclusive(0).label(L01).label(L0)) - .with_system(make_exclusive(4).label(L4)) - .with_system(make_exclusive(3).after(L2).before(L4)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - - world.resource_mut::().0.clear(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(2).label(L234).label(L2)) - .with_system(make_exclusive(1).before(L234).after(L0)) - .with_system(make_exclusive(0).label(L0)) - .with_system(make_exclusive(4).label(L234).label(L4)) - .with_system(make_exclusive(3).label(L234).after(L2).before(L4)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - } - - #[test] - fn exclusive_redundant_constraints() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(2).label(L2).after(L1).before(L3).before(L3)) - .with_system(make_exclusive(1).label(L1).after(L0).after(L0).before(L2)) - .with_system(make_exclusive(0).label(L0).before(L1)) - .with_system(make_exclusive(4).label(L4).after(L3)) - .with_system(make_exclusive(3).label(L3).after(L2).before(L4)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - } - - #[test] - fn exclusive_mixed_across_sets() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(2).label(L2)) - .with_system_set( - SystemSet::new() - .with_system(make_exclusive(0).label(L0)) - .with_system(make_exclusive(4).label(L4)) - .with_system(make_exclusive(3).after(L2).before(L4)), - ) - .with_system(make_exclusive(1).after(L0).before(L2)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - } - - #[test] - fn exclusive_run_criteria() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(0).before(L1)) - .with_system_set( - SystemSet::new() - .with_run_criteria(every_other_time) - .with_system(make_exclusive(1).label(L1)), - ) - .with_system(make_exclusive(2).after(L1)); - stage.run(&mut world); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 0, 2, 0, 1, 2, 0, 2] - ); - } - - #[test] - #[should_panic] - fn exclusive_cycle_1() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel().with_system(make_exclusive(0).label(L0).after(L0)); - stage.run(&mut world); - } - - #[test] - #[should_panic] - fn exclusive_cycle_2() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(0).label(L0).after(L1)) - .with_system(make_exclusive(1).label(L1).after(L0)); - stage.run(&mut world); - } - - #[test] - #[should_panic] - fn exclusive_cycle_3() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_exclusive(0).label(L0)) - .with_system(make_exclusive(1).after(L0).before(L2)) - .with_system(make_exclusive(2).label(L2).before(L0)); - stage.run(&mut world); - } - - #[test] - fn parallel_after() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(1).after(L0).label(L1)) - .with_system(make_parallel(2).after(L1)) - .with_system(make_parallel(0).label(L0)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 2, 0, 1, 2]); - } - - #[test] - fn parallel_before() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(1).label(L1).before(L2)) - .with_system(make_parallel(2).label(L2)) - .with_system(make_parallel(0).before(L1)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 2, 0, 1, 2]); - } - - #[test] - fn parallel_mixed() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(2).label(L2)) - .with_system(make_parallel(1).after(L0).before(L2)) - .with_system(make_parallel(0).label(L0)) - .with_system(make_parallel(4).label(L4)) - .with_system(make_parallel(3).after(L2).before(L4)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - } - - #[test] - fn parallel_multiple_labels() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(1).label(First).after(L0)) - .with_system(make_parallel(2).after(First)) - .with_system(make_parallel(0).label(First).label(L0)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 2, 0, 1, 2]); - - world.resource_mut::().0.clear(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(2).after(L01).label(L2)) - .with_system(make_parallel(1).label(L01).after(L0)) - .with_system(make_parallel(0).label(L01).label(L0)) - .with_system(make_parallel(4).label(L4)) - .with_system(make_parallel(3).after(L2).before(L4)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - - world.resource_mut::().0.clear(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(2).label(L234).label(L2)) - .with_system(make_parallel(1).before(L234).after(L0)) - .with_system(make_parallel(0).label(L0)) - .with_system(make_parallel(4).label(L234).label(L4)) - .with_system(make_parallel(3).label(L234).after(L2).before(L4)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - } - - #[test] - fn parallel_redundant_constraints() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(2).label(L2).after(L1).before(L3).before(L3)) - .with_system(make_parallel(1).label(L1).after(L0).after(L0).before(L2)) - .with_system(make_parallel(0).label(L0).before(L1)) - .with_system(make_parallel(4).label(L4).after(L3)) - .with_system(make_parallel(3).label(L3).after(L2).before(L4)); - stage.run(&mut world); - for container in &stage.parallel { - assert!(container.dependencies().len() <= 1); - } - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - } - - #[test] - fn parallel_mixed_across_sets() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(2).label(L2)) - .with_system_set( - SystemSet::new() - .with_system(make_parallel(0).label(L0)) - .with_system(make_parallel(4).label(L4)) - .with_system(make_parallel(3).after(L2).before(L4)), - ) - .with_system(make_parallel(1).after(L0).before(L2)); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4] - ); - } - - #[derive(RunCriteriaLabel)] - struct EveryOtherTime; - - #[test] - fn parallel_run_criteria() { - let mut world = World::new(); - - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system( - make_parallel(0) - .label(L0) - .with_run_criteria(every_other_time), - ) - .with_system(make_parallel(1).after(L0)); - stage.run(&mut world); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 1, 0, 1, 1]); - - world.resource_mut::().0.clear(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(0).before(L1)) - .with_system_set( - SystemSet::new() - .with_run_criteria(every_other_time) - .with_system(make_parallel(1).label(L1)), - ) - .with_system(make_parallel(2).after(L1)); - stage.run(&mut world); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 0, 2, 0, 1, 2, 0, 2] - ); - - // Reusing criteria. - world.resource_mut::().0.clear(); - let mut stage = SystemStage::parallel() - .with_system_run_criteria(every_other_time.label(EveryOtherTime)) - .with_system(make_parallel(0).before(L1)) - .with_system(make_parallel(1).label(L1).with_run_criteria(EveryOtherTime)) - .with_system( - make_parallel(2) - .label(L2) - .after(L1) - .with_run_criteria(EveryOtherTime), - ) - .with_system(make_parallel(3).after(L2)); - stage.run(&mut world); - stage.run(&mut world); - stage.set_executor(Box::::default()); - stage.run(&mut world); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 0, 3, 0, 1, 2, 3, 0, 3] - ); - assert_eq!(stage.run_criteria.len(), 1); - - // Piping criteria. - world.resource_mut::().0.clear(); - fn eot_piped(input: In, has_ran: Local) -> ShouldRun { - if let ShouldRun::Yes | ShouldRun::YesAndCheckAgain = input.0 { - every_other_time(has_ran) - } else { - ShouldRun::No - } - } - - #[derive(RunCriteriaLabel)] - struct Piped; - - let mut stage = SystemStage::parallel() - .with_system(make_parallel(0).label(L0)) - .with_system( - make_parallel(1) - .label(L1) - .after(L0) - .with_run_criteria(every_other_time.label(EveryOtherTime)), - ) - .with_system( - make_parallel(2) - .label(L2) - .after(L1) - .with_run_criteria(RunCriteria::pipe(EveryOtherTime, eot_piped)), - ) - .with_system( - make_parallel(3) - .label(L3) - .after(L2) - .with_run_criteria(RunCriteria::pipe(EveryOtherTime, eot_piped).label(Piped)), - ) - .with_system(make_parallel(4).after(L3).with_run_criteria(Piped)); - for _ in 0..4 { - stage.run(&mut world); - } - stage.set_executor(Box::::default()); - for _ in 0..5 { - stage.run(&mut world); - } - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 4, 0, 0, 1, 0, 0, 1, 2, 3, 4, 0, 0, 1, 0, 0, 1, 2, 3, 4] - ); - assert_eq!(stage.run_criteria.len(), 3); - - // Discarding extra criteria with matching labels. - world.resource_mut::().0.clear(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(0).before(L1)) - .with_system( - make_parallel(1) - .label(L1) - .with_run_criteria(every_other_time.label_discard_if_duplicate(EveryOtherTime)), - ) - .with_system( - make_parallel(2) - .label(L2) - .after(L1) - .with_run_criteria(every_other_time.label_discard_if_duplicate(EveryOtherTime)), - ) - .with_system(make_parallel(3).after(L2)); - stage.run(&mut world); - stage.run(&mut world); - // false positive, `Box::default` cannot coerce `SingleThreadedExecutor` to `dyn ParallelSystemExectutor` - stage.set_executor(Box::::default()); - stage.run(&mut world); - stage.run(&mut world); - assert_eq!( - world.resource::().0, - vec![0, 1, 2, 3, 0, 3, 0, 1, 2, 3, 0, 3] - ); - assert_eq!(stage.run_criteria.len(), 1); - } - - #[test] - #[should_panic] - fn duplicate_run_criteria_label_panic() { - let mut world = World::new(); - let mut stage = SystemStage::parallel() - .with_system_run_criteria(every_other_time.label(EveryOtherTime)) - .with_system_run_criteria(every_other_time.label(EveryOtherTime)); - stage.run(&mut world); - } - - #[test] - #[should_panic] - fn parallel_cycle_1() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel().with_system(make_parallel(0).label(L0).after(L0)); - stage.run(&mut world); - } - - #[test] - #[should_panic] - fn parallel_cycle_2() { - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(0).label(L0).after(L1)) - .with_system(make_parallel(1).label(L1).after(L0)); - stage.run(&mut world); - } - - #[test] - #[should_panic] - fn parallel_cycle_3() { - let mut world = World::new(); - - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(make_parallel(0).label(L0)) - .with_system(make_parallel(1).after(L0).before(L2)) - .with_system(make_parallel(2).label(L2).before(L0)); - stage.run(&mut world); - } - - #[test] - #[should_panic] - fn multiple_worlds_same_stage() { - let mut world_a = World::default(); - let mut world_b = World::default(); - let mut stage = SystemStage::parallel(); - stage.run(&mut world_a); - stage.run(&mut world_b); - } - - #[test] - fn archetype_update_single_executor() { - fn query_count_system(mut entity_count: ResMut, query: Query) { - *entity_count = R(query.iter().count()); - } - - let mut world = World::new(); - world.insert_resource(R(0)); - let mut stage = SystemStage::single(query_count_system); - - let entity = world.spawn_empty().id(); - stage.run(&mut world); - assert_eq!(world.resource::().0, 1); - - world.get_entity_mut(entity).unwrap().insert(W(1)); - stage.run(&mut world); - assert_eq!(world.resource::().0, 1); - } - - #[test] - fn archetype_update_parallel_executor() { - fn query_count_system(mut entity_count: ResMut, query: Query) { - *entity_count = R(query.iter().count()); - } - - let mut world = World::new(); - world.insert_resource(R(0)); - let mut stage = SystemStage::parallel(); - stage.add_system(query_count_system); - - let entity = world.spawn_empty().id(); - stage.run(&mut world); - assert_eq!(world.resource::().0, 1); - - world.get_entity_mut(entity).unwrap().insert(W(1)); - stage.run(&mut world); - assert_eq!(world.resource::().0, 1); - } - - #[test] - fn run_criteria_with_query() { - use crate::{self as bevy_ecs, component::Component}; - - #[derive(Component)] - struct Foo; - - fn even_number_of_entities_critiera(query: Query<&Foo>) -> ShouldRun { - if query.iter().len() % 2 == 0 { - ShouldRun::Yes - } else { - ShouldRun::No - } - } - - fn spawn_entity(mut commands: crate::prelude::Commands) { - commands.spawn(Foo); - } - - fn count_entities(query: Query<&Foo>, mut res: ResMut) { - res.0.push(query.iter().len()); - } - - #[derive(SystemLabel)] - struct Spawn; - - let mut world = World::new(); - world.init_resource::(); - let mut stage = SystemStage::parallel() - .with_system(spawn_entity.label(Spawn)) - .with_system_set( - SystemSet::new() - .with_run_criteria(even_number_of_entities_critiera) - .with_system(count_entities.before(Spawn)), - ); - stage.run(&mut world); - stage.run(&mut world); - stage.run(&mut world); - stage.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 2]); - } - - #[test] - fn stage_run_criteria_with_query() { - use crate::{self as bevy_ecs, component::Component}; - - #[derive(Component)] - struct Foo; - - fn even_number_of_entities_critiera(query: Query<&Foo>) -> ShouldRun { - if query.iter().len() % 2 == 0 { - ShouldRun::Yes - } else { - ShouldRun::No - } - } - - fn spawn_entity(mut commands: crate::prelude::Commands) { - commands.spawn(Foo); - } - - fn count_entities(query: Query<&Foo>, mut res: ResMut) { - res.0.push(query.iter().len()); - } - - let mut world = World::new(); - world.init_resource::(); - let mut stage_spawn = SystemStage::parallel().with_system(spawn_entity); - let mut stage_count = SystemStage::parallel() - .with_run_criteria(even_number_of_entities_critiera) - .with_system(count_entities); - stage_count.run(&mut world); - stage_spawn.run(&mut world); - stage_count.run(&mut world); - stage_spawn.run(&mut world); - stage_count.run(&mut world); - stage_spawn.run(&mut world); - stage_count.run(&mut world); - stage_spawn.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 2]); - } -} diff --git a/crates/bevy_ecs/src/schedule/state.rs b/crates/bevy_ecs/src/schedule/state.rs deleted file mode 100644 index 3ad5dbef5f059..0000000000000 --- a/crates/bevy_ecs/src/schedule/state.rs +++ /dev/null @@ -1,757 +0,0 @@ -use crate::{ - schedule::{ - RunCriteriaDescriptor, RunCriteriaDescriptorCoercion, RunCriteriaLabel, ShouldRun, - SystemSet, - }, - system::{In, IntoPipeSystem, Local, Res, ResMut, Resource}, -}; -use std::{ - any::TypeId, - fmt::{self, Debug}, - hash::Hash, -}; -// Required for derive macros -use crate as bevy_ecs; - -pub trait StateData: Send + Sync + Clone + Eq + Debug + Hash + 'static {} -impl StateData for T where T: Send + Sync + Clone + Eq + Debug + Hash + 'static {} - -/// ### Stack based state machine -/// -/// This state machine has four operations: Push, Pop, Set and Replace. -/// * Push pushes a new state to the state stack, pausing the previous state -/// * Pop removes the current state, and unpauses the last paused state -/// * Set replaces the active state with a new one -/// * Replace unwinds the state stack, and replaces the entire stack with a single new state -#[derive(Debug, Resource)] -pub struct State { - transition: Option>, - /// The current states in the stack. - /// - /// There is always guaranteed to be at least one. - stack: Vec, - scheduled: Option>, - end_next_loop: bool, -} - -#[derive(Debug)] -enum StateTransition { - PreStartup, - Startup, - // The parameter order is always (leaving, entering) - ExitingToResume(T, T), - ExitingFull(T, T), - Entering(T, T), - Resuming(T, T), - Pausing(T, T), -} - -#[derive(Debug)] -enum ScheduledOperation { - Set(T), - Replace(T), - Pop, - Push(T), -} - -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -struct DriverLabel(TypeId, &'static str); -impl RunCriteriaLabel for DriverLabel { - fn type_id(&self) -> core::any::TypeId { - self.0 - } - fn as_str(&self) -> &'static str { - self.1 - } -} - -impl DriverLabel { - fn of() -> Self { - Self(TypeId::of::(), std::any::type_name::()) - } -} - -impl State -where - T: StateData, -{ - pub fn on_update(pred: T) -> RunCriteriaDescriptor { - (move |state: Res>| { - state.stack.last().unwrap() == &pred && state.transition.is_none() - }) - .pipe(should_run_adapter::) - .after(DriverLabel::of::()) - } - - pub fn on_inactive_update(pred: T) -> RunCriteriaDescriptor { - (move |state: Res>, mut is_inactive: Local| match &state.transition { - Some(StateTransition::Pausing(ref relevant, _)) - | Some(StateTransition::Resuming(_, ref relevant)) => { - if relevant == &pred { - *is_inactive = !*is_inactive; - } - false - } - Some(_) => false, - None => *is_inactive, - }) - .pipe(should_run_adapter::) - .after(DriverLabel::of::()) - } - - pub fn on_in_stack_update(pred: T) -> RunCriteriaDescriptor { - (move |state: Res>, mut is_in_stack: Local| match &state.transition { - Some(StateTransition::Entering(ref relevant, _)) - | Some(StateTransition::ExitingToResume(_, ref relevant)) - | Some(StateTransition::ExitingFull(_, ref relevant)) => { - if relevant == &pred { - *is_in_stack = !*is_in_stack; - } - false - } - Some(StateTransition::Startup) => { - if state.stack.last().unwrap() == &pred { - *is_in_stack = !*is_in_stack; - } - false - } - Some(_) => false, - None => *is_in_stack, - }) - .pipe(should_run_adapter::) - .after(DriverLabel::of::()) - } - - pub fn on_enter(pred: T) -> RunCriteriaDescriptor { - (move |state: Res>| { - state - .transition - .as_ref() - .map_or(false, |transition| match transition { - StateTransition::Entering(_, entering) => entering == &pred, - StateTransition::Startup => state.stack.last().unwrap() == &pred, - _ => false, - }) - }) - .pipe(should_run_adapter::) - .after(DriverLabel::of::()) - } - - pub fn on_exit(pred: T) -> RunCriteriaDescriptor { - (move |state: Res>| { - state - .transition - .as_ref() - .map_or(false, |transition| match transition { - StateTransition::ExitingToResume(exiting, _) - | StateTransition::ExitingFull(exiting, _) => exiting == &pred, - _ => false, - }) - }) - .pipe(should_run_adapter::) - .after(DriverLabel::of::()) - } - - pub fn on_pause(pred: T) -> RunCriteriaDescriptor { - (move |state: Res>| { - state - .transition - .as_ref() - .map_or(false, |transition| match transition { - StateTransition::Pausing(pausing, _) => pausing == &pred, - _ => false, - }) - }) - .pipe(should_run_adapter::) - .after(DriverLabel::of::()) - } - - pub fn on_resume(pred: T) -> RunCriteriaDescriptor { - (move |state: Res>| { - state - .transition - .as_ref() - .map_or(false, |transition| match transition { - StateTransition::Resuming(_, resuming) => resuming == &pred, - _ => false, - }) - }) - .pipe(should_run_adapter::) - .after(DriverLabel::of::()) - } - - pub fn on_update_set(s: T) -> SystemSet { - SystemSet::new().with_run_criteria(Self::on_update(s)) - } - - pub fn on_inactive_update_set(s: T) -> SystemSet { - SystemSet::new().with_run_criteria(Self::on_inactive_update(s)) - } - - pub fn on_enter_set(s: T) -> SystemSet { - SystemSet::new().with_run_criteria(Self::on_enter(s)) - } - - pub fn on_exit_set(s: T) -> SystemSet { - SystemSet::new().with_run_criteria(Self::on_exit(s)) - } - - pub fn on_pause_set(s: T) -> SystemSet { - SystemSet::new().with_run_criteria(Self::on_pause(s)) - } - - pub fn on_resume_set(s: T) -> SystemSet { - SystemSet::new().with_run_criteria(Self::on_resume(s)) - } - - /// Creates a driver set for the State. - /// - /// Important note: this set must be inserted **before** all other state-dependant sets to work - /// properly! - pub fn get_driver() -> SystemSet { - SystemSet::default().with_run_criteria(state_cleaner::.label(DriverLabel::of::())) - } - - pub fn new(initial: T) -> Self { - Self { - stack: vec![initial], - transition: Some(StateTransition::PreStartup), - scheduled: None, - end_next_loop: false, - } - } - - /// Schedule a state change that replaces the active state with the given state. - /// This will fail if there is a scheduled operation, pending transition, or if the given - /// `state` matches the current state - pub fn set(&mut self, state: T) -> Result<(), StateError> { - if self.stack.last().unwrap() == &state { - return Err(StateError::AlreadyInState); - } - - if self.scheduled.is_some() || self.transition.is_some() { - return Err(StateError::StateAlreadyQueued); - } - - self.scheduled = Some(ScheduledOperation::Set(state)); - Ok(()) - } - - /// Same as [`Self::set`], but if there is already a next state, it will be overwritten - /// instead of failing - pub fn overwrite_set(&mut self, state: T) -> Result<(), StateError> { - if self.stack.last().unwrap() == &state { - return Err(StateError::AlreadyInState); - } - - self.scheduled = Some(ScheduledOperation::Set(state)); - Ok(()) - } - - /// Schedule a state change that replaces the full stack with the given state. - /// This will fail if there is a scheduled operation, pending transition, or if the given - /// `state` matches the current state - pub fn replace(&mut self, state: T) -> Result<(), StateError> { - if self.stack.last().unwrap() == &state { - return Err(StateError::AlreadyInState); - } - - if self.scheduled.is_some() || self.transition.is_some() { - return Err(StateError::StateAlreadyQueued); - } - - self.scheduled = Some(ScheduledOperation::Replace(state)); - Ok(()) - } - - /// Same as [`Self::replace`], but if there is already a next state, it will be overwritten - /// instead of failing - pub fn overwrite_replace(&mut self, state: T) -> Result<(), StateError> { - if self.stack.last().unwrap() == &state { - return Err(StateError::AlreadyInState); - } - - self.scheduled = Some(ScheduledOperation::Replace(state)); - Ok(()) - } - - /// Same as [`Self::set`], but does a push operation instead of a next operation - pub fn push(&mut self, state: T) -> Result<(), StateError> { - if self.stack.last().unwrap() == &state { - return Err(StateError::AlreadyInState); - } - - if self.scheduled.is_some() || self.transition.is_some() { - return Err(StateError::StateAlreadyQueued); - } - - self.scheduled = Some(ScheduledOperation::Push(state)); - Ok(()) - } - - /// Same as [`Self::push`], but if there is already a next state, it will be overwritten - /// instead of failing - pub fn overwrite_push(&mut self, state: T) -> Result<(), StateError> { - if self.stack.last().unwrap() == &state { - return Err(StateError::AlreadyInState); - } - - self.scheduled = Some(ScheduledOperation::Push(state)); - Ok(()) - } - - /// Same as [`Self::set`], but does a pop operation instead of a set operation - pub fn pop(&mut self) -> Result<(), StateError> { - if self.scheduled.is_some() || self.transition.is_some() { - return Err(StateError::StateAlreadyQueued); - } - - if self.stack.len() == 1 { - return Err(StateError::StackEmpty); - } - - self.scheduled = Some(ScheduledOperation::Pop); - Ok(()) - } - - /// Same as [`Self::pop`], but if there is already a next state, it will be overwritten - /// instead of failing - pub fn overwrite_pop(&mut self) -> Result<(), StateError> { - if self.stack.len() == 1 { - return Err(StateError::StackEmpty); - } - self.scheduled = Some(ScheduledOperation::Pop); - Ok(()) - } - - /// Schedule a state change that restarts the active state. - /// This will fail if there is a scheduled operation or a pending transition - pub fn restart(&mut self) -> Result<(), StateError> { - if self.scheduled.is_some() || self.transition.is_some() { - return Err(StateError::StateAlreadyQueued); - } - - let state = self.stack.last().unwrap(); - self.scheduled = Some(ScheduledOperation::Set(state.clone())); - Ok(()) - } - - /// Same as [`Self::restart`], but if there is already a scheduled state operation, - /// it will be overwritten instead of failing - pub fn overwrite_restart(&mut self) { - let state = self.stack.last().unwrap(); - self.scheduled = Some(ScheduledOperation::Set(state.clone())); - } - - pub fn current(&self) -> &T { - self.stack.last().unwrap() - } - - pub fn inactives(&self) -> &[T] { - self.stack.split_last().map(|(_, rest)| rest).unwrap() - } - - /// Clears the scheduled state operation. - pub fn clear_schedule(&mut self) { - self.scheduled = None; - } -} - -#[derive(Debug)] -pub enum StateError { - AlreadyInState, - StateAlreadyQueued, - StackEmpty, -} - -impl std::error::Error for StateError {} - -impl fmt::Display for StateError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - StateError::AlreadyInState => { - write!(f, "Attempted to change the state to the current state.") - } - StateError::StateAlreadyQueued => write!( - f, - "Attempted to queue a state change, but there was already a state queued." - ), - StateError::StackEmpty => { - write!(f, "Attempted to queue a pop, but there is nothing to pop.") - } - } - } -} - -fn should_run_adapter(In(cmp_result): In, state: Res>) -> ShouldRun { - if state.end_next_loop { - return ShouldRun::No; - } - if cmp_result { - ShouldRun::YesAndCheckAgain - } else { - ShouldRun::NoAndCheckAgain - } -} - -fn state_cleaner( - mut state: ResMut>, - mut prep_exit: Local, -) -> ShouldRun { - if *prep_exit { - *prep_exit = false; - if state.scheduled.is_none() { - state.end_next_loop = true; - return ShouldRun::YesAndCheckAgain; - } - } else if state.end_next_loop { - state.end_next_loop = false; - return ShouldRun::No; - } - match state.scheduled.take() { - Some(ScheduledOperation::Set(next)) => { - state.transition = Some(StateTransition::ExitingFull( - state.stack.last().unwrap().clone(), - next, - )); - } - Some(ScheduledOperation::Replace(next)) => { - if state.stack.len() <= 1 { - state.transition = Some(StateTransition::ExitingFull( - state.stack.last().unwrap().clone(), - next, - )); - } else { - state.scheduled = Some(ScheduledOperation::Replace(next)); - match state.transition.take() { - Some(StateTransition::ExitingToResume(p, n)) => { - state.stack.pop(); - state.transition = Some(StateTransition::Resuming(p, n)); - } - _ => { - state.transition = Some(StateTransition::ExitingToResume( - state.stack[state.stack.len() - 1].clone(), - state.stack[state.stack.len() - 2].clone(), - )); - } - } - } - } - Some(ScheduledOperation::Push(next)) => { - let last_type_id = state.stack.last().unwrap().clone(); - state.transition = Some(StateTransition::Pausing(last_type_id, next)); - } - Some(ScheduledOperation::Pop) => { - state.transition = Some(StateTransition::ExitingToResume( - state.stack[state.stack.len() - 1].clone(), - state.stack[state.stack.len() - 2].clone(), - )); - } - None => match state.transition.take() { - Some(StateTransition::ExitingFull(p, n)) => { - state.transition = Some(StateTransition::Entering(p, n.clone())); - *state.stack.last_mut().unwrap() = n; - } - Some(StateTransition::Pausing(p, n)) => { - state.transition = Some(StateTransition::Entering(p, n.clone())); - state.stack.push(n); - } - Some(StateTransition::ExitingToResume(p, n)) => { - state.stack.pop(); - state.transition = Some(StateTransition::Resuming(p, n)); - } - Some(StateTransition::PreStartup) => { - state.transition = Some(StateTransition::Startup); - } - _ => {} - }, - }; - if state.transition.is_none() { - *prep_exit = true; - } - - ShouldRun::YesAndCheckAgain -} - -#[cfg(test)] -mod test { - use super::*; - use crate::prelude::*; - - #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] - enum MyState { - S1, - S2, - S3, - S4, - S5, - S6, - Final, - } - - #[test] - fn state_test() { - #[derive(Resource, Default)] - struct NameList(Vec<&'static str>); - - let mut world = World::default(); - - world.init_resource::(); - world.insert_resource(State::new(MyState::S1)); - - let mut stage = SystemStage::parallel(); - - #[derive(SystemLabel)] - enum Inactive { - S4, - S5, - } - - stage.add_system_set(State::::get_driver()); - stage - .add_system_set( - State::on_enter_set(MyState::S1) - .with_system(|mut r: ResMut| r.0.push("startup")), - ) - .add_system_set(State::on_update_set(MyState::S1).with_system( - |mut r: ResMut, mut s: ResMut>| { - r.0.push("update S1"); - s.overwrite_replace(MyState::S2).unwrap(); - }, - )) - .add_system_set( - State::on_enter_set(MyState::S2) - .with_system(|mut r: ResMut| r.0.push("enter S2")), - ) - .add_system_set(State::on_update_set(MyState::S2).with_system( - |mut r: ResMut, mut s: ResMut>| { - r.0.push("update S2"); - s.overwrite_replace(MyState::S3).unwrap(); - }, - )) - .add_system_set( - State::on_exit_set(MyState::S2) - .with_system(|mut r: ResMut| r.0.push("exit S2")), - ) - .add_system_set( - State::on_enter_set(MyState::S3) - .with_system(|mut r: ResMut| r.0.push("enter S3")), - ) - .add_system_set(State::on_update_set(MyState::S3).with_system( - |mut r: ResMut, mut s: ResMut>| { - r.0.push("update S3"); - s.overwrite_push(MyState::S4).unwrap(); - }, - )) - .add_system_set( - State::on_pause_set(MyState::S3) - .with_system(|mut r: ResMut| r.0.push("pause S3")), - ) - .add_system_set(State::on_update_set(MyState::S4).with_system( - |mut r: ResMut, mut s: ResMut>| { - r.0.push("update S4"); - s.overwrite_push(MyState::S5).unwrap(); - }, - )) - .add_system_set(State::on_inactive_update_set(MyState::S4).with_system( - (|mut r: ResMut| r.0.push("inactive S4")).label(Inactive::S4), - )) - .add_system_set( - State::on_update_set(MyState::S5).with_system( - (|mut r: ResMut, mut s: ResMut>| { - r.0.push("update S5"); - s.overwrite_push(MyState::S6).unwrap(); - }) - .after(Inactive::S4), - ), - ) - .add_system_set( - State::on_inactive_update_set(MyState::S5).with_system( - (|mut r: ResMut| r.0.push("inactive S5")) - .label(Inactive::S5) - .after(Inactive::S4), - ), - ) - .add_system_set( - State::on_update_set(MyState::S6).with_system( - (|mut r: ResMut, mut s: ResMut>| { - r.0.push("update S6"); - s.overwrite_push(MyState::Final).unwrap(); - }) - .after(Inactive::S5), - ), - ) - .add_system_set( - State::on_resume_set(MyState::S4) - .with_system(|mut r: ResMut| r.0.push("resume S4")), - ) - .add_system_set( - State::on_exit_set(MyState::S5) - .with_system(|mut r: ResMut| r.0.push("exit S4")), - ); - - const EXPECTED: &[&str] = &[ - // - "startup", - "update S1", - // - "enter S2", - "update S2", - // - "exit S2", - "enter S3", - "update S3", - // - "pause S3", - "update S4", - // - "inactive S4", - "update S5", - // - "inactive S4", - "inactive S5", - "update S6", - // - "inactive S4", - "inactive S5", - ]; - - stage.run(&mut world); - let mut collected = world.resource_mut::(); - let mut count = 0; - for (found, expected) in collected.0.drain(..).zip(EXPECTED) { - assert_eq!(found, *expected); - count += 1; - } - // If not equal, some elements weren't executed - assert_eq!(EXPECTED.len(), count); - assert_eq!( - world.resource::>().current(), - &MyState::Final - ); - } - - #[test] - fn issue_1753() { - #[derive(Clone, PartialEq, Eq, Debug, Hash)] - enum AppState { - Main, - } - - #[derive(Resource)] - struct Flag(bool); - - #[derive(Resource)] - struct Name(&'static str); - - fn should_run_once(mut flag: ResMut, test_name: Res) { - assert!(!flag.0, "{:?}", test_name.0); - flag.0 = true; - } - - let mut world = World::new(); - world.insert_resource(State::new(AppState::Main)); - world.insert_resource(Flag(false)); - world.insert_resource(Name("control")); - let mut stage = SystemStage::parallel().with_system(should_run_once); - stage.run(&mut world); - assert!(world.resource::().0, "after control"); - - world.insert_resource(Flag(false)); - world.insert_resource(Name("test")); - let mut stage = SystemStage::parallel() - .with_system_set(State::::get_driver()) - .with_system(should_run_once); - stage.run(&mut world); - assert!(world.resource::().0, "after test"); - } - - #[test] - fn restart_state_tests() { - #[derive(Clone, PartialEq, Eq, Debug, Hash)] - enum LoadState { - Load, - Finish, - } - - #[derive(PartialEq, Eq, Debug)] - enum LoadStatus { - EnterLoad, - ExitLoad, - EnterFinish, - } - - #[derive(Resource, Default)] - struct LoadStatusStack(Vec); - - let mut world = World::new(); - world.init_resource::(); - world.insert_resource(State::new(LoadState::Load)); - - let mut stage = SystemStage::parallel(); - stage.add_system_set(State::::get_driver()); - - // Systems to track loading status - stage - .add_system_set( - State::on_enter_set(LoadState::Load) - .with_system(|mut r: ResMut| r.0.push(LoadStatus::EnterLoad)), - ) - .add_system_set( - State::on_exit_set(LoadState::Load) - .with_system(|mut r: ResMut| r.0.push(LoadStatus::ExitLoad)), - ) - .add_system_set( - State::on_enter_set(LoadState::Finish).with_system( - |mut r: ResMut| r.0.push(LoadStatus::EnterFinish), - ), - ); - - stage.run(&mut world); - - // A. Restart state - let mut state = world.resource_mut::>(); - let result = state.restart(); - assert!(matches!(result, Ok(()))); - stage.run(&mut world); - - // B. Restart state (overwrite schedule) - let mut state = world.resource_mut::>(); - state.set(LoadState::Finish).unwrap(); - state.overwrite_restart(); - stage.run(&mut world); - - // C. Fail restart state (transition already scheduled) - let mut state = world.resource_mut::>(); - state.set(LoadState::Finish).unwrap(); - let result = state.restart(); - assert!(matches!(result, Err(StateError::StateAlreadyQueued))); - stage.run(&mut world); - - const EXPECTED: &[LoadStatus] = &[ - LoadStatus::EnterLoad, - // A - LoadStatus::ExitLoad, - LoadStatus::EnterLoad, - // B - LoadStatus::ExitLoad, - LoadStatus::EnterLoad, - // C - LoadStatus::ExitLoad, - LoadStatus::EnterFinish, - ]; - - let mut collected = world.resource_mut::(); - let mut count = 0; - for (found, expected) in collected.0.drain(..).zip(EXPECTED) { - assert_eq!(found, *expected); - count += 1; - } - // If not equal, some elements weren't executed - assert_eq!(EXPECTED.len(), count); - assert_eq!( - world.resource::>().current(), - &LoadState::Finish - ); - } -} diff --git a/crates/bevy_ecs/src/schedule/system_container.rs b/crates/bevy_ecs/src/schedule/system_container.rs deleted file mode 100644 index 4178d14c24c38..0000000000000 --- a/crates/bevy_ecs/src/schedule/system_container.rs +++ /dev/null @@ -1,111 +0,0 @@ -use crate::{ - component::ComponentId, - query::Access, - schedule::{ - AmbiguityDetection, GraphNode, RunCriteriaLabelId, SystemDescriptor, SystemLabelId, - }, - system::System, -}; -use core::fmt::Debug; -use std::borrow::Cow; - -pub struct SystemContainer { - system: Box>, - pub(crate) run_criteria_index: Option, - pub(crate) run_criteria_label: Option, - pub(crate) should_run: bool, - is_exclusive: bool, - dependencies: Vec, - labels: Vec, - before: Vec, - after: Vec, - pub(crate) ambiguity_detection: AmbiguityDetection, -} - -impl SystemContainer { - pub(crate) fn from_descriptor(descriptor: SystemDescriptor) -> Self { - SystemContainer { - system: descriptor.system, - should_run: false, - run_criteria_index: None, - run_criteria_label: None, - dependencies: Vec::new(), - labels: descriptor.labels, - before: descriptor.before, - after: descriptor.after, - ambiguity_detection: descriptor.ambiguity_detection, - is_exclusive: descriptor.exclusive_insertion_point.is_some(), - } - } - - pub fn name(&self) -> Cow<'static, str> { - GraphNode::name(self) - } - - pub fn system(&self) -> &dyn System { - &*self.system - } - - pub fn system_mut(&mut self) -> &mut dyn System { - &mut *self.system - } - - pub fn should_run(&self) -> bool { - self.should_run - } - - pub fn dependencies(&self) -> &[usize] { - &self.dependencies - } - - pub fn set_dependencies(&mut self, dependencies: impl IntoIterator) { - self.dependencies.clear(); - self.dependencies.extend(dependencies); - } - - pub fn run_criteria(&self) -> Option { - self.run_criteria_index - } - - pub fn set_run_criteria(&mut self, index: usize) { - self.run_criteria_index = Some(index); - } - - pub fn run_criteria_label(&self) -> Option<&RunCriteriaLabelId> { - self.run_criteria_label.as_ref() - } - - pub fn component_access(&self) -> &Access { - self.system().component_access() - } - - pub fn is_exclusive(&self) -> bool { - self.is_exclusive - } -} - -impl Debug for SystemContainer { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{{{:?}}}", &self.system()) - } -} - -impl GraphNode for SystemContainer { - type Label = SystemLabelId; - - fn name(&self) -> Cow<'static, str> { - self.system().name() - } - - fn labels(&self) -> &[SystemLabelId] { - &self.labels - } - - fn before(&self) -> &[SystemLabelId] { - &self.before - } - - fn after(&self) -> &[SystemLabelId] { - &self.after - } -} diff --git a/crates/bevy_ecs/src/schedule/system_descriptor.rs b/crates/bevy_ecs/src/schedule/system_descriptor.rs deleted file mode 100644 index 8628069e9042a..0000000000000 --- a/crates/bevy_ecs/src/schedule/system_descriptor.rs +++ /dev/null @@ -1,274 +0,0 @@ -use crate::{ - schedule::{IntoRunCriteria, RunCriteriaDescriptorOrLabel, SystemLabel, SystemLabelId}, - system::{AsSystemLabel, BoxedSystem, IntoSystem}, -}; - -/// Configures ambiguity detection for a single system. -#[derive(Debug, Default)] -pub(crate) enum AmbiguityDetection { - #[default] - Check, - IgnoreAll, - /// Ignore systems with any of these labels. - IgnoreWithLabel(Vec), -} - -/// Encapsulates a system and information on when it run in a `SystemStage`. -/// -/// Systems can be inserted into 4 different groups within the stage: -/// * Parallel, accepts non-exclusive systems. -/// * At start, accepts exclusive systems; runs before parallel systems. -/// * Before commands, accepts exclusive systems; runs after parallel systems, but before their -/// command buffers are applied. -/// * At end, accepts exclusive systems; runs after parallel systems' command buffers have -/// been applied. -/// -/// Systems can have one or more labels attached to them; other systems in the same group -/// can then specify that they have to run before or after systems with that label using the -/// `before` and `after` methods. -/// -/// # Example -/// ``` -/// # use bevy_ecs::prelude::*; -/// # fn do_something() {} -/// # fn do_the_other_thing() {} -/// # fn do_something_else() {} -/// #[derive(SystemLabel, Debug, Clone, PartialEq, Eq, Hash)] -/// struct Something; -/// -/// SystemStage::parallel() -/// .with_system(do_something.label(Something)) -/// .with_system(do_the_other_thing.after(Something)) -/// .with_system(do_something_else.at_end()); -/// ``` -#[derive(Debug)] -pub struct SystemDescriptor { - pub(crate) system: BoxedSystem<(), ()>, - pub(crate) exclusive_insertion_point: Option, - pub(crate) run_criteria: Option, - pub(crate) labels: Vec, - pub(crate) before: Vec, - pub(crate) after: Vec, - pub(crate) ambiguity_detection: AmbiguityDetection, -} - -impl SystemDescriptor { - fn new(system: BoxedSystem<(), ()>) -> SystemDescriptor { - SystemDescriptor { - labels: system.default_labels(), - exclusive_insertion_point: if system.is_exclusive() { - Some(ExclusiveInsertionPoint::AtStart) - } else { - None - }, - system, - run_criteria: None, - before: Vec::new(), - after: Vec::new(), - ambiguity_detection: Default::default(), - } - } -} - -pub trait IntoSystemDescriptor { - fn into_descriptor(self) -> SystemDescriptor; - /// Assigns a run criteria to the system. Can be a new descriptor or a label of a - /// run criteria defined elsewhere. - fn with_run_criteria( - self, - run_criteria: impl IntoRunCriteria, - ) -> SystemDescriptor; - - /// Assigns a label to the system; there can be more than one, and it doesn't have to be unique. - fn label(self, label: impl SystemLabel) -> SystemDescriptor; - - /// Specifies that the system should run before systems with the given label. - fn before(self, label: impl AsSystemLabel) -> SystemDescriptor; - - /// Specifies that the system should run after systems with the given label. - fn after(self, label: impl AsSystemLabel) -> SystemDescriptor; - - /// Marks this system as ambiguous with any system with the specified label. - /// This means that execution order between these systems does not matter, - /// which allows [some warnings](crate::schedule::ReportExecutionOrderAmbiguities) to be silenced. - fn ambiguous_with(self, label: impl AsSystemLabel) -> SystemDescriptor; - - /// Specifies that this system should opt out of - /// [execution order ambiguity detection](crate::schedule::ReportExecutionOrderAmbiguities). - fn ignore_all_ambiguities(self) -> SystemDescriptor; - - /// Specifies that the system should run with other exclusive systems at the start of stage. - fn at_start(self) -> SystemDescriptor; - - /// Specifies that the system should run with other exclusive systems after the parallel - /// systems and before command buffer application. - fn before_commands(self) -> SystemDescriptor; - - /// Specifies that the system should run with other exclusive systems at the end of stage. - fn at_end(self) -> SystemDescriptor; -} - -impl IntoSystemDescriptor<()> for SystemDescriptor { - fn with_run_criteria( - mut self, - run_criteria: impl IntoRunCriteria, - ) -> SystemDescriptor { - self.run_criteria = Some(run_criteria.into()); - self - } - - fn label(mut self, label: impl SystemLabel) -> SystemDescriptor { - self.labels.push(label.as_label()); - self - } - - fn before(mut self, label: impl AsSystemLabel) -> SystemDescriptor { - self.before.push(label.as_system_label().as_label()); - self - } - - fn after(mut self, label: impl AsSystemLabel) -> SystemDescriptor { - self.after.push(label.as_system_label().as_label()); - self - } - - fn ambiguous_with(mut self, label: impl AsSystemLabel) -> SystemDescriptor { - match &mut self.ambiguity_detection { - detection @ AmbiguityDetection::Check => { - *detection = - AmbiguityDetection::IgnoreWithLabel(vec![label.as_system_label().as_label()]); - } - AmbiguityDetection::IgnoreWithLabel(labels) => { - labels.push(label.as_system_label().as_label()); - } - // This descriptor is already ambiguous with everything. - AmbiguityDetection::IgnoreAll => {} - } - self - } - - fn ignore_all_ambiguities(mut self) -> SystemDescriptor { - self.ambiguity_detection = AmbiguityDetection::IgnoreAll; - self - } - - fn at_start(mut self) -> SystemDescriptor { - self.exclusive_insertion_point = Some(ExclusiveInsertionPoint::AtStart); - self - } - - fn before_commands(mut self) -> SystemDescriptor { - self.exclusive_insertion_point = Some(ExclusiveInsertionPoint::BeforeCommands); - self - } - - fn at_end(mut self) -> SystemDescriptor { - self.exclusive_insertion_point = Some(ExclusiveInsertionPoint::AtEnd); - self - } - - fn into_descriptor(self) -> SystemDescriptor { - self - } -} - -impl IntoSystemDescriptor for S -where - S: IntoSystem<(), (), Params>, -{ - fn with_run_criteria( - self, - run_criteria: impl IntoRunCriteria, - ) -> SystemDescriptor { - SystemDescriptor::new(Box::new(IntoSystem::into_system(self))) - .with_run_criteria(run_criteria) - } - - fn label(self, label: impl SystemLabel) -> SystemDescriptor { - SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).label(label) - } - - fn before(self, label: impl AsSystemLabel) -> SystemDescriptor { - SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).before(label) - } - - fn after(self, label: impl AsSystemLabel) -> SystemDescriptor { - SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).after(label) - } - - fn ambiguous_with(self, label: impl AsSystemLabel) -> SystemDescriptor { - SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).ambiguous_with(label) - } - - fn ignore_all_ambiguities(self) -> SystemDescriptor { - SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).ignore_all_ambiguities() - } - - fn at_start(self) -> SystemDescriptor { - SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).at_start() - } - - fn before_commands(self) -> SystemDescriptor { - SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).before_commands() - } - - fn at_end(self) -> SystemDescriptor { - SystemDescriptor::new(Box::new(IntoSystem::into_system(self))).at_end() - } - - fn into_descriptor(self) -> SystemDescriptor { - SystemDescriptor::new(Box::new(IntoSystem::into_system(self))) - } -} - -impl IntoSystemDescriptor<()> for BoxedSystem<(), ()> { - fn with_run_criteria( - self, - run_criteria: impl IntoRunCriteria, - ) -> SystemDescriptor { - SystemDescriptor::new(self).with_run_criteria(run_criteria) - } - - fn label(self, label: impl SystemLabel) -> SystemDescriptor { - SystemDescriptor::new(self).label(label) - } - - fn before(self, label: impl AsSystemLabel) -> SystemDescriptor { - SystemDescriptor::new(self).before(label) - } - - fn after(self, label: impl AsSystemLabel) -> SystemDescriptor { - SystemDescriptor::new(self).after(label) - } - - fn ambiguous_with(self, label: impl AsSystemLabel) -> SystemDescriptor { - SystemDescriptor::new(self).ambiguous_with(label) - } - - fn ignore_all_ambiguities(self) -> SystemDescriptor { - SystemDescriptor::new(self).ignore_all_ambiguities() - } - - fn at_start(self) -> SystemDescriptor { - SystemDescriptor::new(self).at_start() - } - - fn before_commands(self) -> SystemDescriptor { - SystemDescriptor::new(self).before_commands() - } - - fn at_end(self) -> SystemDescriptor { - SystemDescriptor::new(self).at_end() - } - - fn into_descriptor(self) -> SystemDescriptor { - SystemDescriptor::new(self) - } -} - -#[derive(Debug, Clone, Copy)] -pub(crate) enum ExclusiveInsertionPoint { - AtStart, - BeforeCommands, - AtEnd, -} diff --git a/crates/bevy_ecs/src/schedule/system_set.rs b/crates/bevy_ecs/src/schedule/system_set.rs deleted file mode 100644 index 95eded570fe97..0000000000000 --- a/crates/bevy_ecs/src/schedule/system_set.rs +++ /dev/null @@ -1,116 +0,0 @@ -use crate::schedule::{ - IntoRunCriteria, IntoSystemDescriptor, RunCriteriaDescriptorOrLabel, State, StateData, - SystemDescriptor, SystemLabel, SystemLabelId, -}; -use crate::system::AsSystemLabel; - -/// A builder for describing several systems at the same time. -#[derive(Default)] -pub struct SystemSet { - pub(crate) systems: Vec, - pub(crate) run_criteria: Option, - pub(crate) labels: Vec, - pub(crate) before: Vec, - pub(crate) after: Vec, -} - -impl SystemSet { - pub fn new() -> Self { - Default::default() - } - - pub fn on_update(s: T) -> SystemSet - where - T: StateData, - { - Self::new().with_run_criteria(State::::on_update(s)) - } - - pub fn on_inactive_update(s: T) -> SystemSet - where - T: StateData, - { - Self::new().with_run_criteria(State::::on_inactive_update(s)) - } - - pub fn on_in_stack_update(s: T) -> SystemSet - where - T: StateData, - { - Self::new().with_run_criteria(State::::on_in_stack_update(s)) - } - - pub fn on_enter(s: T) -> SystemSet - where - T: StateData, - { - Self::new().with_run_criteria(State::::on_enter(s)) - } - - pub fn on_exit(s: T) -> SystemSet - where - T: StateData, - { - Self::new().with_run_criteria(State::::on_exit(s)) - } - - pub fn on_pause(s: T) -> SystemSet - where - T: StateData, - { - Self::new().with_run_criteria(State::::on_pause(s)) - } - - pub fn on_resume(s: T) -> SystemSet - where - T: StateData, - { - Self::new().with_run_criteria(State::::on_resume(s)) - } - - #[must_use] - pub fn with_system(mut self, system: impl IntoSystemDescriptor) -> Self { - self.systems.push(system.into_descriptor()); - self - } - - #[must_use] - pub fn with_run_criteria(mut self, run_criteria: impl IntoRunCriteria) -> Self { - self.run_criteria = Some(run_criteria.into()); - self - } - - #[must_use] - pub fn label(mut self, label: impl SystemLabel) -> Self { - self.labels.push(label.as_label()); - self - } - - #[must_use] - pub fn before(mut self, label: impl AsSystemLabel) -> Self { - self.before.push(label.as_system_label().as_label()); - self - } - - #[must_use] - pub fn after(mut self, label: impl AsSystemLabel) -> Self { - self.after.push(label.as_system_label().as_label()); - self - } - - pub(crate) fn bake(self) -> (Option, Vec) { - let SystemSet { - mut systems, - run_criteria, - labels, - before, - after, - } = self; - for descriptor in &mut systems { - descriptor.labels.extend(labels.iter().cloned()); - descriptor.before.extend(before.iter().cloned()); - descriptor.after.extend(after.iter().cloned()); - } - (run_criteria, systems) - } -} From 4173274e64f0ae95005b99667af663da51d78bd2 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 09:57:05 -0500 Subject: [PATCH 002/247] Rename schedule_v3 module to schedule --- crates/bevy_ecs/src/schedule/condition.rs | 97 ++ crates/bevy_ecs/src/schedule/config.rs | 649 ++++++++++ crates/bevy_ecs/src/schedule/executor/mod.rs | 90 ++ .../src/schedule/executor/multi_threaded.rs | 575 +++++++++ .../bevy_ecs/src/schedule/executor/simple.rs | 111 ++ .../src/schedule/executor/single_threaded.rs | 137 ++ crates/bevy_ecs/src/schedule/graph_utils.rs | 233 ++++ crates/bevy_ecs/src/schedule/migration.rs | 38 + crates/bevy_ecs/src/schedule/mod.rs | 622 ++++++++++ crates/bevy_ecs/src/schedule/schedule.rs | 1099 +++++++++++++++++ crates/bevy_ecs/src/schedule/state.rs | 64 + .../src/system/exclusive_function_system.rs | 4 +- crates/bevy_ecs/src/system/function_system.rs | 4 +- crates/bevy_ecs/src/system/system.rs | 2 +- crates/bevy_ecs/src/system/system_piping.rs | 2 +- crates/bevy_ecs/src/world/mod.rs | 2 +- 16 files changed, 3722 insertions(+), 7 deletions(-) create mode 100644 crates/bevy_ecs/src/schedule/condition.rs create mode 100644 crates/bevy_ecs/src/schedule/config.rs create mode 100644 crates/bevy_ecs/src/schedule/executor/mod.rs create mode 100644 crates/bevy_ecs/src/schedule/executor/multi_threaded.rs create mode 100644 crates/bevy_ecs/src/schedule/executor/simple.rs create mode 100644 crates/bevy_ecs/src/schedule/executor/single_threaded.rs create mode 100644 crates/bevy_ecs/src/schedule/graph_utils.rs create mode 100644 crates/bevy_ecs/src/schedule/migration.rs create mode 100644 crates/bevy_ecs/src/schedule/mod.rs create mode 100644 crates/bevy_ecs/src/schedule/schedule.rs create mode 100644 crates/bevy_ecs/src/schedule/state.rs diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs new file mode 100644 index 0000000000000..edd0456a004c7 --- /dev/null +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -0,0 +1,97 @@ +pub use common_conditions::*; + +use crate::system::BoxedSystem; + +pub type BoxedCondition = BoxedSystem<(), bool>; + +/// A system that determines if one or more scheduled systems should run. +/// +/// Implemented for functions and closures that convert into [`System`](crate::system::System) +/// with [read-only](crate::system::ReadOnlySystemParam) parameters. +pub trait Condition: sealed::Condition {} + +impl Condition for F where F: sealed::Condition {} + +mod sealed { + use crate::system::{IntoSystem, IsFunctionSystem, ReadOnlySystemParam, SystemParamFunction}; + + pub trait Condition: IntoSystem<(), bool, Params> {} + + impl Condition<(IsFunctionSystem, Params, Marker)> for F + where + F: SystemParamFunction<(), bool, Params, Marker> + Send + Sync + 'static, + Params: ReadOnlySystemParam + 'static, + Marker: 'static, + { + } +} + +mod common_conditions { + use crate::schedule::{State, States}; + use crate::system::{Res, Resource}; + + /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` + /// if the resource exists. + pub fn resource_exists() -> impl FnMut(Option>) -> bool + where + T: Resource, + { + move |res: Option>| res.is_some() + } + + /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` + /// if the resource is equal to `value`. + /// + /// # Panics + /// + /// The condition will panic if the resource does not exist. + pub fn resource_equals(value: T) -> impl FnMut(Res) -> bool + where + T: Resource + PartialEq, + { + move |res: Res| *res == value + } + + /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` + /// if the resource exists and is equal to `value`. + /// + /// The condition will return `false` if the resource does not exist. + pub fn resource_exists_and_equals(value: T) -> impl FnMut(Option>) -> bool + where + T: Resource + PartialEq, + { + move |res: Option>| match res { + Some(res) => *res == value, + None => false, + } + } + + /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` + /// if the state machine exists. + pub fn state_exists() -> impl FnMut(Option>>) -> bool { + move |current_state: Option>>| current_state.is_some() + } + + /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` + /// if the state machine is currently in `state`. + /// + /// # Panics + /// + /// The condition will panic if the resource does not exist. + pub fn state_equals(state: S) -> impl FnMut(Res>) -> bool { + move |current_state: Res>| current_state.0 == state + } + + /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` + /// if the state machine exists and is currently in `state`. + /// + /// The condition will return `false` if the state does not exist. + pub fn state_exists_and_equals( + state: S, + ) -> impl FnMut(Option>>) -> bool { + move |current_state: Option>>| match current_state { + Some(current_state) => current_state.0 == state, + None => false, + } + } +} diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs new file mode 100644 index 0000000000000..6f0672f4bf1b3 --- /dev/null +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -0,0 +1,649 @@ +use bevy_ecs_macros::all_tuples; +use bevy_utils::default; + +use crate::{ + schedule::{ + condition::{BoxedCondition, Condition}, + graph_utils::{Ambiguity, Dependency, DependencyKind, GraphInfo}, + set::{BoxedSystemSet, IntoSystemSet, SystemSet}, + }, + system::{BoxedSystem, IntoSystem, System}, +}; + +/// A [`SystemSet`] with scheduling metadata. +pub struct SystemSetConfig { + pub(super) set: BoxedSystemSet, + pub(super) graph_info: GraphInfo, + pub(super) conditions: Vec, +} + +impl SystemSetConfig { + fn new(set: BoxedSystemSet) -> Self { + // system type sets are automatically populated + // to avoid unintentionally broad changes, they cannot be configured + assert!( + !set.is_system_type(), + "configuring system type sets is not allowed" + ); + + Self { + set, + graph_info: GraphInfo { + sets: Vec::new(), + dependencies: Vec::new(), + ambiguous_with: default(), + }, + conditions: Vec::new(), + } + } +} + +/// A [`System`] with scheduling metadata. +pub struct SystemConfig { + pub(super) system: BoxedSystem, + pub(super) graph_info: GraphInfo, + pub(super) conditions: Vec, +} + +impl SystemConfig { + fn new(system: BoxedSystem) -> Self { + // include system in its default sets + let sets = system.default_system_sets().into_iter().collect(); + Self { + system, + graph_info: GraphInfo { + sets, + dependencies: Vec::new(), + ambiguous_with: default(), + }, + conditions: Vec::new(), + } + } +} + +fn new_condition

(condition: impl Condition

) -> BoxedCondition { + let condition_system = IntoSystem::into_system(condition); + assert!( + condition_system.is_send(), + "Condition `{}` accesses thread-local resources. This is not currently supported.", + condition_system.name() + ); + + Box::new(condition_system) +} + +fn ambiguous_with(graph_info: &mut GraphInfo, set: BoxedSystemSet) { + match &mut graph_info.ambiguous_with { + detection @ Ambiguity::Check => { + *detection = Ambiguity::IgnoreWithSet(vec![set]); + } + Ambiguity::IgnoreWithSet(ambiguous_with) => { + ambiguous_with.push(set); + } + Ambiguity::IgnoreAll => (), + } +} + +/// Types that can be converted into a [`SystemSetConfig`]. +/// +/// This has been implemented for all types that implement [`SystemSet`] and boxed trait objects. +pub trait IntoSystemSetConfig: sealed::IntoSystemSetConfig { + /// Convert into a [`SystemSetConfig`]. + #[doc(hidden)] + fn into_config(self) -> SystemSetConfig; + /// Add to the provided `set`. + fn in_set(self, set: impl SystemSet) -> SystemSetConfig; + /// Run before all systems in `set`. + fn before(self, set: impl IntoSystemSet) -> SystemSetConfig; + /// Run after all systems in `set`. + fn after(self, set: impl IntoSystemSet) -> SystemSetConfig; + /// Run the systems in this set only if the [`Condition`] is `true`. + /// + /// The `Condition` will be evaluated at most once (per schedule run), + /// the first time a system in this set prepares to run. + fn run_if

(self, condition: impl Condition

) -> SystemSetConfig; + /// Suppress warnings and errors that would result from systems in this set having ambiguities + /// (conflicting access but indeterminate order) with systems in `set`. + fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemSetConfig; + /// Suppress warnings and errors that would result from systems in this set having ambiguities + /// (conflicting access but indeterminate order) with any other system. + fn ambiguous_with_all(self) -> SystemSetConfig; +} + +impl IntoSystemSetConfig for S +where + S: SystemSet + sealed::IntoSystemSetConfig, +{ + fn into_config(self) -> SystemSetConfig { + SystemSetConfig::new(Box::new(self)) + } + + fn in_set(self, set: impl SystemSet) -> SystemSetConfig { + SystemSetConfig::new(Box::new(self)).in_set(set) + } + + fn before(self, set: impl IntoSystemSet) -> SystemSetConfig { + SystemSetConfig::new(Box::new(self)).before(set) + } + + fn after(self, set: impl IntoSystemSet) -> SystemSetConfig { + SystemSetConfig::new(Box::new(self)).after(set) + } + + fn run_if

(self, condition: impl Condition

) -> SystemSetConfig { + SystemSetConfig::new(Box::new(self)).run_if(condition) + } + + fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemSetConfig { + SystemSetConfig::new(Box::new(self)).ambiguous_with(set) + } + + fn ambiguous_with_all(self) -> SystemSetConfig { + SystemSetConfig::new(Box::new(self)).ambiguous_with_all() + } +} + +impl IntoSystemSetConfig for BoxedSystemSet { + fn into_config(self) -> SystemSetConfig { + SystemSetConfig::new(self) + } + + fn in_set(self, set: impl SystemSet) -> SystemSetConfig { + SystemSetConfig::new(self).in_set(set) + } + + fn before(self, set: impl IntoSystemSet) -> SystemSetConfig { + SystemSetConfig::new(self).before(set) + } + + fn after(self, set: impl IntoSystemSet) -> SystemSetConfig { + SystemSetConfig::new(self).after(set) + } + + fn run_if

(self, condition: impl Condition

) -> SystemSetConfig { + SystemSetConfig::new(self).run_if(condition) + } + + fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemSetConfig { + SystemSetConfig::new(self).ambiguous_with(set) + } + + fn ambiguous_with_all(self) -> SystemSetConfig { + SystemSetConfig::new(self).ambiguous_with_all() + } +} + +impl IntoSystemSetConfig for SystemSetConfig { + fn into_config(self) -> Self { + self + } + + fn in_set(mut self, set: impl SystemSet) -> Self { + assert!( + !set.is_system_type(), + "adding arbitrary systems to a system type set is not allowed" + ); + self.graph_info.sets.push(Box::new(set)); + self + } + + fn before(mut self, set: impl IntoSystemSet) -> Self { + self.graph_info.dependencies.push(Dependency::new( + DependencyKind::Before, + Box::new(set.into_system_set()), + )); + self + } + + fn after(mut self, set: impl IntoSystemSet) -> Self { + self.graph_info.dependencies.push(Dependency::new( + DependencyKind::After, + Box::new(set.into_system_set()), + )); + self + } + + fn run_if

(mut self, condition: impl Condition

) -> Self { + self.conditions.push(new_condition(condition)); + self + } + + fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { + ambiguous_with(&mut self.graph_info, Box::new(set.into_system_set())); + self + } + + fn ambiguous_with_all(mut self) -> Self { + self.graph_info.ambiguous_with = Ambiguity::IgnoreAll; + self + } +} + +/// Types that can be converted into a [`SystemConfig`]. +/// +/// This has been implemented for boxed [`System`](crate::system::System) +/// trait objects and all functions that turn into such. +pub trait IntoSystemConfig: sealed::IntoSystemConfig { + /// Convert into a [`SystemConfig`]. + #[doc(hidden)] + fn into_config(self) -> SystemConfig; + /// Add to `set` membership. + fn in_set(self, set: impl SystemSet) -> SystemConfig; + /// Run before all systems in `set`. + fn before(self, set: impl IntoSystemSet) -> SystemConfig; + /// Run after all systems in `set`. + fn after(self, set: impl IntoSystemSet) -> SystemConfig; + /// Run only if the [`Condition`] is `true`. + /// + /// The `Condition` will be evaluated at most once (per schedule run), + /// when the system prepares to run. + fn run_if

(self, condition: impl Condition

) -> SystemConfig; + /// Suppress warnings and errors that would result from this system having ambiguities + /// (conflicting access but indeterminate order) with systems in `set`. + fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemConfig; + /// Suppress warnings and errors that would result from this system having ambiguities + /// (conflicting access but indeterminate order) with any other system. + fn ambiguous_with_all(self) -> SystemConfig; +} + +impl IntoSystemConfig for F +where + F: IntoSystem<(), (), Params> + sealed::IntoSystemConfig, +{ + fn into_config(self) -> SystemConfig { + SystemConfig::new(Box::new(IntoSystem::into_system(self))) + } + + fn in_set(self, set: impl SystemSet) -> SystemConfig { + SystemConfig::new(Box::new(IntoSystem::into_system(self))).in_set(set) + } + + fn before(self, set: impl IntoSystemSet) -> SystemConfig { + SystemConfig::new(Box::new(IntoSystem::into_system(self))).before(set) + } + + fn after(self, set: impl IntoSystemSet) -> SystemConfig { + SystemConfig::new(Box::new(IntoSystem::into_system(self))).after(set) + } + + fn run_if

(self, condition: impl Condition

) -> SystemConfig { + SystemConfig::new(Box::new(IntoSystem::into_system(self))).run_if(condition) + } + + fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemConfig { + SystemConfig::new(Box::new(IntoSystem::into_system(self))).ambiguous_with(set) + } + + fn ambiguous_with_all(self) -> SystemConfig { + SystemConfig::new(Box::new(IntoSystem::into_system(self))).ambiguous_with_all() + } +} + +impl IntoSystemConfig<()> for BoxedSystem<(), ()> { + fn into_config(self) -> SystemConfig { + SystemConfig::new(self) + } + + fn in_set(self, set: impl SystemSet) -> SystemConfig { + SystemConfig::new(self).in_set(set) + } + + fn before(self, set: impl IntoSystemSet) -> SystemConfig { + SystemConfig::new(self).before(set) + } + + fn after(self, set: impl IntoSystemSet) -> SystemConfig { + SystemConfig::new(self).after(set) + } + + fn run_if

(self, condition: impl Condition

) -> SystemConfig { + SystemConfig::new(self).run_if(condition) + } + + fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemConfig { + SystemConfig::new(self).ambiguous_with(set) + } + + fn ambiguous_with_all(self) -> SystemConfig { + SystemConfig::new(self).ambiguous_with_all() + } +} + +impl IntoSystemConfig<()> for SystemConfig { + fn into_config(self) -> Self { + self + } + + fn in_set(mut self, set: impl SystemSet) -> Self { + assert!( + !set.is_system_type(), + "adding arbitrary systems to a system type set is not allowed" + ); + self.graph_info.sets.push(Box::new(set)); + self + } + + fn before(mut self, set: impl IntoSystemSet) -> Self { + self.graph_info.dependencies.push(Dependency::new( + DependencyKind::Before, + Box::new(set.into_system_set()), + )); + self + } + + fn after(mut self, set: impl IntoSystemSet) -> Self { + self.graph_info.dependencies.push(Dependency::new( + DependencyKind::After, + Box::new(set.into_system_set()), + )); + self + } + + fn run_if

(mut self, condition: impl Condition

) -> Self { + self.conditions.push(new_condition(condition)); + self + } + + fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { + ambiguous_with(&mut self.graph_info, Box::new(set.into_system_set())); + self + } + + fn ambiguous_with_all(mut self) -> Self { + self.graph_info.ambiguous_with = Ambiguity::IgnoreAll; + self + } +} + +// only `System` system objects can be scheduled +mod sealed { + use crate::{ + schedule::{BoxedSystemSet, SystemSet}, + system::{BoxedSystem, IntoSystem}, + }; + + use super::{SystemConfig, SystemSetConfig}; + + pub trait IntoSystemConfig {} + + impl> IntoSystemConfig for F {} + + impl IntoSystemConfig<()> for BoxedSystem<(), ()> {} + + impl IntoSystemConfig<()> for SystemConfig {} + + pub trait IntoSystemSetConfig {} + + impl IntoSystemSetConfig for S {} + + impl IntoSystemSetConfig for BoxedSystemSet {} + + impl IntoSystemSetConfig for SystemSetConfig {} +} + +/// A collection of [`SystemConfig`]. +pub struct SystemConfigs { + pub(super) systems: Vec, + /// If `true`, adds `before -> after` ordering constraints between the successive elements. + pub(super) chained: bool, +} + +/// Types that can convert into a [`SystemConfigs`]. +pub trait IntoSystemConfigs +where + Self: Sized, +{ + /// Convert into a [`SystemConfigs`]. + #[doc(hidden)] + fn into_configs(self) -> SystemConfigs; + + /// Add these systems to the provided `set`. + fn in_set(self, set: impl SystemSet) -> SystemConfigs { + self.into_configs().in_set(set) + } + + /// Run before all systems in `set`. + fn before(self, set: impl IntoSystemSet) -> SystemConfigs { + self.into_configs().before(set) + } + + /// Run after all systems in `set`. + fn after(self, set: impl IntoSystemSet) -> SystemConfigs { + self.into_configs().after(set) + } + + /// Suppress warnings and errors that would result from these systems having ambiguities + /// (conflicting access but indeterminate order) with systems in `set`. + fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemConfigs { + self.into_configs().ambiguous_with(set) + } + + /// Suppress warnings and errors that would result from these systems having ambiguities + /// (conflicting access but indeterminate order) with any other system. + fn ambiguous_with_all(self) -> SystemConfigs { + self.into_configs().ambiguous_with_all() + } + + /// Treat this collection as a sequence of systems. + /// + /// Ordering constraints will be applied between the successive elements. + fn chain(self) -> SystemConfigs { + self.into_configs().chain() + } +} + +impl IntoSystemConfigs<()> for SystemConfigs { + fn into_configs(self) -> Self { + self + } + + fn in_set(mut self, set: impl SystemSet) -> Self { + assert!( + !set.is_system_type(), + "adding arbitrary systems to a system type set is not allowed" + ); + for config in &mut self.systems { + config.graph_info.sets.push(set.dyn_clone()); + } + + self + } + + fn before(mut self, set: impl IntoSystemSet) -> Self { + let set = set.into_system_set(); + for config in &mut self.systems { + config + .graph_info + .dependencies + .push(Dependency::new(DependencyKind::Before, set.dyn_clone())); + } + + self + } + + fn after(mut self, set: impl IntoSystemSet) -> Self { + let set = set.into_system_set(); + for config in &mut self.systems { + config + .graph_info + .dependencies + .push(Dependency::new(DependencyKind::After, set.dyn_clone())); + } + + self + } + + fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { + let set = set.into_system_set(); + for config in &mut self.systems { + ambiguous_with(&mut config.graph_info, set.dyn_clone()); + } + + self + } + + fn ambiguous_with_all(mut self) -> Self { + for config in &mut self.systems { + config.graph_info.ambiguous_with = Ambiguity::IgnoreAll; + } + + self + } + + fn chain(mut self) -> Self { + self.chained = true; + self + } +} + +/// A collection of [`SystemSetConfig`]. +pub struct SystemSetConfigs { + pub(super) sets: Vec, + /// If `true`, adds `before -> after` ordering constraints between the successive elements. + pub(super) chained: bool, +} + +/// Types that can convert into a [`SystemSetConfigs`]. +pub trait IntoSystemSetConfigs +where + Self: Sized, +{ + /// Convert into a [`SystemSetConfigs`]. + #[doc(hidden)] + fn into_configs(self) -> SystemSetConfigs; + + /// Add these system sets to the provided `set`. + fn in_set(self, set: impl SystemSet) -> SystemSetConfigs { + self.into_configs().in_set(set) + } + + /// Run before all systems in `set`. + fn before(self, set: impl IntoSystemSet) -> SystemSetConfigs { + self.into_configs().before(set) + } + + /// Run after all systems in `set`. + fn after(self, set: impl IntoSystemSet) -> SystemSetConfigs { + self.into_configs().after(set) + } + + /// Suppress warnings and errors that would result from systems in these sets having ambiguities + /// (conflicting access but indeterminate order) with systems in `set`. + fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemSetConfigs { + self.into_configs().ambiguous_with(set) + } + + /// Suppress warnings and errors that would result from systems in these sets having ambiguities + /// (conflicting access but indeterminate order) with any other system. + fn ambiguous_with_all(self) -> SystemSetConfigs { + self.into_configs().ambiguous_with_all() + } + + /// Treat this collection as a sequence of system sets. + /// + /// Ordering constraints will be applied between the successive elements. + fn chain(self) -> SystemSetConfigs { + self.into_configs().chain() + } +} + +impl IntoSystemSetConfigs for SystemSetConfigs { + fn into_configs(self) -> Self { + self + } + + fn in_set(mut self, set: impl SystemSet) -> Self { + assert!( + !set.is_system_type(), + "adding arbitrary systems to a system type set is not allowed" + ); + for config in &mut self.sets { + config.graph_info.sets.push(set.dyn_clone()); + } + + self + } + + fn before(mut self, set: impl IntoSystemSet) -> Self { + let set = set.into_system_set(); + for config in &mut self.sets { + config + .graph_info + .dependencies + .push(Dependency::new(DependencyKind::Before, set.dyn_clone())); + } + + self + } + + fn after(mut self, set: impl IntoSystemSet) -> Self { + let set = set.into_system_set(); + for config in &mut self.sets { + config + .graph_info + .dependencies + .push(Dependency::new(DependencyKind::After, set.dyn_clone())); + } + + self + } + + fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { + let set = set.into_system_set(); + for config in &mut self.sets { + ambiguous_with(&mut config.graph_info, set.dyn_clone()); + } + + self + } + + fn ambiguous_with_all(mut self) -> Self { + for config in &mut self.sets { + config.graph_info.ambiguous_with = Ambiguity::IgnoreAll; + } + + self + } + + fn chain(mut self) -> Self { + self.chained = true; + self + } +} + +macro_rules! impl_system_collection { + ($(($param: ident, $sys: ident)),*) => { + impl<$($param, $sys),*> IntoSystemConfigs<($($param,)*)> for ($($sys,)*) + where + $($sys: IntoSystemConfig<$param>),* + { + #[allow(non_snake_case)] + fn into_configs(self) -> SystemConfigs { + let ($($sys,)*) = self; + SystemConfigs { + systems: vec![$($sys.into_config(),)*], + chained: false, + } + } + } + } +} + +macro_rules! impl_system_set_collection { + ($($set: ident),*) => { + impl<$($set: IntoSystemSetConfig),*> IntoSystemSetConfigs for ($($set,)*) + { + #[allow(non_snake_case)] + fn into_configs(self) -> SystemSetConfigs { + let ($($set,)*) = self; + SystemSetConfigs { + sets: vec![$($set.into_config(),)*], + chained: false, + } + } + } + } +} + +all_tuples!(impl_system_collection, 0, 15, P, S); +all_tuples!(impl_system_set_collection, 0, 15, S); diff --git a/crates/bevy_ecs/src/schedule/executor/mod.rs b/crates/bevy_ecs/src/schedule/executor/mod.rs new file mode 100644 index 0000000000000..c4b894c3ad146 --- /dev/null +++ b/crates/bevy_ecs/src/schedule/executor/mod.rs @@ -0,0 +1,90 @@ +mod multi_threaded; +mod simple; +mod single_threaded; + +pub use self::multi_threaded::MultiThreadedExecutor; +pub use self::simple::SimpleExecutor; +pub use self::single_threaded::SingleThreadedExecutor; + +use fixedbitset::FixedBitSet; + +use crate::{ + schedule::{BoxedCondition, NodeId}, + system::BoxedSystem, + world::World, +}; + +/// Types that can run a [`SystemSchedule`] on a [`World`]. +pub(super) trait SystemExecutor: Send + Sync { + fn kind(&self) -> ExecutorKind; + fn init(&mut self, schedule: &SystemSchedule); + fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World); +} + +/// Specifies how a [`Schedule`](super::Schedule) will be run. +/// +/// [`MultiThreaded`](ExecutorKind::MultiThreaded) is the default. +#[derive(PartialEq, Eq, Default)] +pub enum ExecutorKind { + /// Runs the schedule using a single thread. + /// + /// Useful if you're dealing with a single-threaded environment, saving your threads for + /// other things, or just trying minimize overhead. + SingleThreaded, + /// Like [`SingleThreaded`](ExecutorKind::SingleThreaded) but calls [`apply_buffers`](crate::system::System::apply_buffers) + /// immediately after running each system. + Simple, + /// Runs the schedule using a thread pool. Non-conflicting systems can run in parallel. + #[default] + MultiThreaded, +} + +/// Holds systems and conditions of a [`Schedule`](super::Schedule) sorted in topological order +/// (along with dependency information for multi-threaded execution). +/// +/// Since the arrays are sorted in the same order, elements are referenced by their index. +/// `FixedBitSet` is used as a smaller, more efficient substitute of `HashSet`. +#[derive(Default)] +pub(super) struct SystemSchedule { + pub(super) systems: Vec, + pub(super) system_conditions: Vec>, + pub(super) set_conditions: Vec>, + pub(super) system_ids: Vec, + pub(super) set_ids: Vec, + pub(super) system_dependencies: Vec, + pub(super) system_dependents: Vec>, + pub(super) sets_of_systems: Vec, + pub(super) systems_in_sets: Vec, +} + +impl SystemSchedule { + pub const fn new() -> Self { + Self { + systems: Vec::new(), + system_conditions: Vec::new(), + set_conditions: Vec::new(), + system_ids: Vec::new(), + set_ids: Vec::new(), + system_dependencies: Vec::new(), + system_dependents: Vec::new(), + sets_of_systems: Vec::new(), + systems_in_sets: Vec::new(), + } + } +} + +/// Instructs the executor to call [`apply_buffers`](crate::system::System::apply_buffers) +/// on the systems that have run but not applied their buffers. +/// +/// **Notes** +/// - This function (currently) does nothing if it's called manually or wrapped inside a [`PipeSystem`](crate::system::PipeSystem). +/// - Modifying a [`Schedule`](super::Schedule) may change the order buffers are applied. +#[allow(unused_variables)] +pub fn apply_system_buffers(world: &mut World) {} + +/// Returns `true` if the [`System`](crate::system::System) is an instance of [`apply_system_buffers`]. +pub(super) fn is_apply_system_buffers(system: &BoxedSystem) -> bool { + use std::any::Any; + // deref to use `System::type_id` instead of `Any::type_id` + system.as_ref().type_id() == apply_system_buffers.type_id() +} diff --git a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs new file mode 100644 index 0000000000000..69e083767c65e --- /dev/null +++ b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs @@ -0,0 +1,575 @@ +use bevy_tasks::{ComputeTaskPool, Scope, TaskPool}; +use bevy_utils::default; +use bevy_utils::syncunsafecell::SyncUnsafeCell; +#[cfg(feature = "trace")] +use bevy_utils::tracing::{info_span, Instrument}; + +use async_channel::{Receiver, Sender}; +use fixedbitset::FixedBitSet; + +use crate::{ + archetype::ArchetypeComponentId, + query::Access, + schedule::{ + is_apply_system_buffers, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule, + }, + system::BoxedSystem, + world::World, +}; + +/// A funky borrow split of [`SystemSchedule`] required by the [`MultiThreadedExecutor`]. +struct SyncUnsafeSchedule<'a> { + systems: &'a [SyncUnsafeCell], + conditions: Conditions<'a>, +} + +struct Conditions<'a> { + system_conditions: &'a mut [Vec], + set_conditions: &'a mut [Vec], + sets_of_systems: &'a [FixedBitSet], + systems_in_sets: &'a [FixedBitSet], +} + +impl SyncUnsafeSchedule<'_> { + fn new(schedule: &mut SystemSchedule) -> SyncUnsafeSchedule<'_> { + SyncUnsafeSchedule { + systems: SyncUnsafeCell::from_mut(schedule.systems.as_mut_slice()).as_slice_of_cells(), + conditions: Conditions { + system_conditions: &mut schedule.system_conditions, + set_conditions: &mut schedule.set_conditions, + sets_of_systems: &schedule.sets_of_systems, + systems_in_sets: &schedule.systems_in_sets, + }, + } + } +} + +/// Per-system data used by the [`MultiThreadedExecutor`]. +// Copied here because it can't be read from the system when it's running. +struct SystemTaskMetadata { + /// The `ArchetypeComponentId` access of the system. + archetype_component_access: Access, + /// Indices of the systems that directly depend on the system. + dependents: Vec, + /// Is `true` if the system does not access `!Send` data. + is_send: bool, + /// Is `true` if the system is exclusive. + is_exclusive: bool, +} + +/// Runs the schedule using a thread pool. Non-conflicting systems can run in parallel. +pub struct MultiThreadedExecutor { + /// Sends system completion events. + sender: Sender, + /// Receives system completion events. + receiver: Receiver, + /// Metadata for scheduling and running system tasks. + system_task_metadata: Vec, + /// Union of the accesses of all currently running systems. + active_access: Access, + /// Returns `true` if a system with non-`Send` access is running. + local_thread_running: bool, + /// Returns `true` if an exclusive system is running. + exclusive_running: bool, + /// The number of systems that are running. + num_running_systems: usize, + /// The number of systems that have completed. + num_completed_systems: usize, + /// The number of dependencies each system has that have not completed. + num_dependencies_remaining: Vec, + /// System sets whose conditions have been evaluated. + evaluated_sets: FixedBitSet, + /// Systems that have no remaining dependencies and are waiting to run. + ready_systems: FixedBitSet, + /// copy of `ready_systems` + ready_systems_copy: FixedBitSet, + /// Systems that are running. + running_systems: FixedBitSet, + /// Systems that got skipped. + skipped_systems: FixedBitSet, + /// Systems whose conditions have been evaluated and were run or skipped. + completed_systems: FixedBitSet, + /// Systems that have run but have not had their buffers applied. + unapplied_systems: FixedBitSet, +} + +impl Default for MultiThreadedExecutor { + fn default() -> Self { + Self::new() + } +} + +impl SystemExecutor for MultiThreadedExecutor { + fn kind(&self) -> ExecutorKind { + ExecutorKind::MultiThreaded + } + + fn init(&mut self, schedule: &SystemSchedule) { + // pre-allocate space + let sys_count = schedule.system_ids.len(); + let set_count = schedule.set_ids.len(); + + self.evaluated_sets = FixedBitSet::with_capacity(set_count); + self.ready_systems = FixedBitSet::with_capacity(sys_count); + self.ready_systems_copy = FixedBitSet::with_capacity(sys_count); + self.running_systems = FixedBitSet::with_capacity(sys_count); + self.completed_systems = FixedBitSet::with_capacity(sys_count); + self.skipped_systems = FixedBitSet::with_capacity(sys_count); + self.unapplied_systems = FixedBitSet::with_capacity(sys_count); + + self.system_task_metadata = Vec::with_capacity(sys_count); + for index in 0..sys_count { + self.system_task_metadata.push(SystemTaskMetadata { + archetype_component_access: default(), + dependents: schedule.system_dependents[index].clone(), + is_send: schedule.systems[index].is_send(), + is_exclusive: schedule.systems[index].is_exclusive(), + }); + } + + self.num_dependencies_remaining = Vec::with_capacity(sys_count); + } + + fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) { + // reset counts + let num_systems = schedule.systems.len(); + self.num_running_systems = 0; + self.num_completed_systems = 0; + self.num_dependencies_remaining.clear(); + self.num_dependencies_remaining + .extend_from_slice(&schedule.system_dependencies); + + for (system_index, dependencies) in self.num_dependencies_remaining.iter_mut().enumerate() { + if *dependencies == 0 { + self.ready_systems.insert(system_index); + } + } + + let world = SyncUnsafeCell::from_mut(world); + let SyncUnsafeSchedule { + systems, + mut conditions, + } = SyncUnsafeSchedule::new(schedule); + + ComputeTaskPool::init(TaskPool::default).scope(|scope| { + // the executor itself is a `Send` future so that it can run + // alongside systems that claim the local thread + let executor = async { + while self.num_completed_systems < num_systems { + // SAFETY: self.ready_systems does not contain running systems + unsafe { + self.spawn_system_tasks(scope, systems, &mut conditions, world); + } + + if self.num_running_systems > 0 { + // wait for systems to complete + let index = self + .receiver + .recv() + .await + .unwrap_or_else(|error| unreachable!("{}", error)); + + self.finish_system_and_signal_dependents(index); + + while let Ok(index) = self.receiver.try_recv() { + self.finish_system_and_signal_dependents(index); + } + + self.rebuild_active_access(); + } + } + + // SAFETY: all systems have completed + let world = unsafe { &mut *world.get() }; + apply_system_buffers(&mut self.unapplied_systems, systems, world); + + debug_assert!(self.ready_systems.is_clear()); + debug_assert!(self.running_systems.is_clear()); + debug_assert!(self.unapplied_systems.is_clear()); + self.active_access.clear(); + self.evaluated_sets.clear(); + self.skipped_systems.clear(); + self.completed_systems.clear(); + }; + + #[cfg(feature = "trace")] + let executor_span = info_span!("schedule_task"); + #[cfg(feature = "trace")] + let executor = executor.instrument(executor_span); + scope.spawn(executor); + }); + } +} + +impl MultiThreadedExecutor { + pub fn new() -> Self { + let (sender, receiver) = async_channel::unbounded(); + Self { + sender, + receiver, + system_task_metadata: Vec::new(), + num_running_systems: 0, + num_completed_systems: 0, + num_dependencies_remaining: Vec::new(), + active_access: default(), + local_thread_running: false, + exclusive_running: false, + evaluated_sets: FixedBitSet::new(), + ready_systems: FixedBitSet::new(), + ready_systems_copy: FixedBitSet::new(), + running_systems: FixedBitSet::new(), + skipped_systems: FixedBitSet::new(), + completed_systems: FixedBitSet::new(), + unapplied_systems: FixedBitSet::new(), + } + } + + /// # Safety + /// Caller must ensure that `self.ready_systems` does not contain any systems that + /// have been mutably borrowed (such as the systems currently running). + unsafe fn spawn_system_tasks<'scope>( + &mut self, + scope: &Scope<'_, 'scope, ()>, + systems: &'scope [SyncUnsafeCell], + conditions: &mut Conditions, + cell: &'scope SyncUnsafeCell, + ) { + if self.exclusive_running { + return; + } + + // can't borrow since loop mutably borrows `self` + let mut ready_systems = std::mem::take(&mut self.ready_systems_copy); + ready_systems.clear(); + ready_systems.union_with(&self.ready_systems); + + for system_index in ready_systems.ones() { + assert!(!self.running_systems.contains(system_index)); + // SAFETY: Caller assured that these systems are not running. + // Therefore, no other reference to this system exists and there is no aliasing. + let system = unsafe { &mut *systems[system_index].get() }; + + // SAFETY: No exclusive system is running. + // Therefore, there is no existing mutable reference to the world. + let world = unsafe { &*cell.get() }; + if !self.can_run(system_index, system, conditions, world) { + // NOTE: exclusive systems with ambiguities are susceptible to + // being significantly displaced here (compared to single-threaded order) + // if systems after them in topological order can run + // if that becomes an issue, `break;` if exclusive system + continue; + } + + self.ready_systems.set(system_index, false); + + if !self.should_run(system_index, system, conditions, world) { + self.skip_system_and_signal_dependents(system_index); + continue; + } + + self.running_systems.insert(system_index); + self.num_running_systems += 1; + + if self.system_task_metadata[system_index].is_exclusive { + // SAFETY: `can_run` confirmed that no systems are running. + // Therefore, there is no existing reference to the world. + unsafe { + let world = &mut *cell.get(); + self.spawn_exclusive_system_task(scope, system_index, systems, world); + } + break; + } + + // SAFETY: No other reference to this system exists. + unsafe { + self.spawn_system_task(scope, system_index, systems, world); + } + } + + // give back + self.ready_systems_copy = ready_systems; + } + + fn can_run( + &mut self, + system_index: usize, + system: &mut BoxedSystem, + conditions: &mut Conditions, + world: &World, + ) -> bool { + #[cfg(feature = "trace")] + let _span = info_span!("check_access", name = &*system.name()).entered(); + + let system_meta = &self.system_task_metadata[system_index]; + if system_meta.is_exclusive && self.num_running_systems > 0 { + return false; + } + + if !system_meta.is_send && self.local_thread_running { + return false; + } + + // TODO: an earlier out if world's archetypes did not change + for set_idx in conditions.sets_of_systems[system_index].difference(&self.evaluated_sets) { + for condition in &mut conditions.set_conditions[set_idx] { + condition.update_archetype_component_access(world); + if !condition + .archetype_component_access() + .is_compatible(&self.active_access) + { + return false; + } + } + } + + for condition in &mut conditions.system_conditions[system_index] { + condition.update_archetype_component_access(world); + if !condition + .archetype_component_access() + .is_compatible(&self.active_access) + { + return false; + } + } + + if !self.skipped_systems.contains(system_index) { + system.update_archetype_component_access(world); + if !system + .archetype_component_access() + .is_compatible(&self.active_access) + { + return false; + } + + // PERF: use an optimized clear() + extend() operation + let meta_access = + &mut self.system_task_metadata[system_index].archetype_component_access; + meta_access.clear(); + meta_access.extend(system.archetype_component_access()); + } + + true + } + + fn should_run( + &mut self, + system_index: usize, + _system: &BoxedSystem, + conditions: &mut Conditions, + world: &World, + ) -> bool { + #[cfg(feature = "trace")] + let _span = info_span!("check_conditions", name = &*_system.name()).entered(); + + let mut should_run = !self.skipped_systems.contains(system_index); + for set_idx in conditions.sets_of_systems[system_index].ones() { + if self.evaluated_sets.contains(set_idx) { + continue; + } + + // evaluate system set's conditions + let set_conditions_met = + evaluate_and_fold_conditions(&mut conditions.set_conditions[set_idx], world); + + if !set_conditions_met { + self.skipped_systems + .union_with(&conditions.systems_in_sets[set_idx]); + } + + should_run &= set_conditions_met; + self.evaluated_sets.insert(set_idx); + } + + // evaluate system's conditions + let system_conditions_met = + evaluate_and_fold_conditions(&mut conditions.system_conditions[system_index], world); + + if !system_conditions_met { + self.skipped_systems.insert(system_index); + } + + should_run &= system_conditions_met; + + should_run + } + + /// # Safety + /// Caller must not alias systems that are running. + unsafe fn spawn_system_task<'scope>( + &mut self, + scope: &Scope<'_, 'scope, ()>, + system_index: usize, + systems: &'scope [SyncUnsafeCell], + world: &'scope World, + ) { + // SAFETY: this system is not running, no other reference exists + let system = unsafe { &mut *systems[system_index].get() }; + + #[cfg(feature = "trace")] + let task_span = info_span!("system_task", name = &*system.name()); + #[cfg(feature = "trace")] + let system_span = info_span!("system", name = &*system.name()); + + let sender = self.sender.clone(); + let task = async move { + #[cfg(feature = "trace")] + let system_guard = system_span.enter(); + // SAFETY: access is compatible + unsafe { system.run_unsafe((), world) }; + #[cfg(feature = "trace")] + drop(system_guard); + sender + .send(system_index) + .await + .unwrap_or_else(|error| unreachable!("{}", error)); + }; + + #[cfg(feature = "trace")] + let task = task.instrument(task_span); + + let system_meta = &self.system_task_metadata[system_index]; + self.active_access + .extend(&system_meta.archetype_component_access); + + if system_meta.is_send { + scope.spawn(task); + } else { + self.local_thread_running = true; + scope.spawn_on_scope(task); + } + } + + /// # Safety + /// Caller must ensure no systems are currently borrowed. + unsafe fn spawn_exclusive_system_task<'scope>( + &mut self, + scope: &Scope<'_, 'scope, ()>, + system_index: usize, + systems: &'scope [SyncUnsafeCell], + world: &'scope mut World, + ) { + // SAFETY: this system is not running, no other reference exists + let system = unsafe { &mut *systems[system_index].get() }; + + #[cfg(feature = "trace")] + let task_span = info_span!("system_task", name = &*system.name()); + #[cfg(feature = "trace")] + let system_span = info_span!("system", name = &*system.name()); + + let sender = self.sender.clone(); + if is_apply_system_buffers(system) { + // TODO: avoid allocation + let mut unapplied_systems = self.unapplied_systems.clone(); + let task = async move { + #[cfg(feature = "trace")] + let system_guard = system_span.enter(); + apply_system_buffers(&mut unapplied_systems, systems, world); + #[cfg(feature = "trace")] + drop(system_guard); + sender + .send(system_index) + .await + .unwrap_or_else(|error| unreachable!("{}", error)); + }; + + #[cfg(feature = "trace")] + let task = task.instrument(task_span); + scope.spawn_on_scope(task); + } else { + let task = async move { + #[cfg(feature = "trace")] + let system_guard = system_span.enter(); + system.run((), world); + #[cfg(feature = "trace")] + drop(system_guard); + sender + .send(system_index) + .await + .unwrap_or_else(|error| unreachable!("{}", error)); + }; + + #[cfg(feature = "trace")] + let task = task.instrument(task_span); + scope.spawn_on_scope(task); + } + + self.exclusive_running = true; + self.local_thread_running = true; + } + + fn finish_system_and_signal_dependents(&mut self, system_index: usize) { + if self.system_task_metadata[system_index].is_exclusive { + self.exclusive_running = false; + } + + if !self.system_task_metadata[system_index].is_send { + self.local_thread_running = false; + } + + debug_assert!(self.num_running_systems >= 1); + self.num_running_systems -= 1; + self.num_completed_systems += 1; + self.running_systems.set(system_index, false); + self.completed_systems.insert(system_index); + self.unapplied_systems.insert(system_index); + self.signal_dependents(system_index); + } + + fn skip_system_and_signal_dependents(&mut self, system_index: usize) { + self.num_completed_systems += 1; + self.completed_systems.insert(system_index); + self.signal_dependents(system_index); + } + + fn signal_dependents(&mut self, system_index: usize) { + #[cfg(feature = "trace")] + let _span = info_span!("signal_dependents").entered(); + for &dep_idx in &self.system_task_metadata[system_index].dependents { + let remaining = &mut self.num_dependencies_remaining[dep_idx]; + debug_assert!(*remaining >= 1); + *remaining -= 1; + if *remaining == 0 && !self.completed_systems.contains(dep_idx) { + self.ready_systems.insert(dep_idx); + } + } + } + + fn rebuild_active_access(&mut self) { + self.active_access.clear(); + for index in self.running_systems.ones() { + let system_meta = &self.system_task_metadata[index]; + self.active_access + .extend(&system_meta.archetype_component_access); + } + } +} + +fn apply_system_buffers( + unapplied_systems: &mut FixedBitSet, + systems: &[SyncUnsafeCell], + world: &mut World, +) { + for system_index in unapplied_systems.ones() { + // SAFETY: none of these systems are running, no other references exist + let system = unsafe { &mut *systems[system_index].get() }; + #[cfg(feature = "trace")] + let _apply_buffers_span = info_span!("apply_buffers", name = &*system.name()).entered(); + system.apply_buffers(world); + } + + unapplied_systems.clear(); +} + +fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &World) -> bool { + // not short-circuiting is intentional + #[allow(clippy::unnecessary_fold)] + conditions + .iter_mut() + .map(|condition| { + #[cfg(feature = "trace")] + let _condition_span = info_span!("condition", name = &*condition.name()).entered(); + // SAFETY: caller ensures system access is compatible + unsafe { condition.run_unsafe((), world) } + }) + .fold(true, |acc, res| acc && res) +} diff --git a/crates/bevy_ecs/src/schedule/executor/simple.rs b/crates/bevy_ecs/src/schedule/executor/simple.rs new file mode 100644 index 0000000000000..6b68d0a35c923 --- /dev/null +++ b/crates/bevy_ecs/src/schedule/executor/simple.rs @@ -0,0 +1,111 @@ +#[cfg(feature = "trace")] +use bevy_utils::tracing::info_span; +use fixedbitset::FixedBitSet; + +use crate::{ + schedule::{BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule}, + world::World, +}; + +/// A variant of [`SingleThreadedExecutor`](crate::schedule_v3::SingleThreadedExecutor) that calls +/// [`apply_buffers`](crate::system::System::apply_buffers) immediately after running each system. +#[derive(Default)] +pub struct SimpleExecutor { + /// Systems sets whose conditions have been evaluated. + evaluated_sets: FixedBitSet, + /// Systems that have run or been skipped. + completed_systems: FixedBitSet, +} + +impl SystemExecutor for SimpleExecutor { + fn kind(&self) -> ExecutorKind { + ExecutorKind::Simple + } + + fn init(&mut self, schedule: &SystemSchedule) { + let sys_count = schedule.system_ids.len(); + let set_count = schedule.set_ids.len(); + self.evaluated_sets = FixedBitSet::with_capacity(set_count); + self.completed_systems = FixedBitSet::with_capacity(sys_count); + } + + fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) { + for system_index in 0..schedule.systems.len() { + #[cfg(feature = "trace")] + let name = schedule.systems[system_index].name(); + #[cfg(feature = "trace")] + let should_run_span = info_span!("check_conditions", name = &*name).entered(); + + let mut should_run = !self.completed_systems.contains(system_index); + for set_idx in schedule.sets_of_systems[system_index].ones() { + if self.evaluated_sets.contains(set_idx) { + continue; + } + + // evaluate system set's conditions + let set_conditions_met = + evaluate_and_fold_conditions(&mut schedule.set_conditions[set_idx], world); + + if !set_conditions_met { + self.completed_systems + .union_with(&schedule.systems_in_sets[set_idx]); + } + + should_run &= set_conditions_met; + self.evaluated_sets.insert(set_idx); + } + + // evaluate system's conditions + let system_conditions_met = + evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world); + + should_run &= system_conditions_met; + + #[cfg(feature = "trace")] + should_run_span.exit(); + + // system has either been skipped or will run + self.completed_systems.insert(system_index); + + if !should_run { + continue; + } + + let system = &mut schedule.systems[system_index]; + #[cfg(feature = "trace")] + let system_span = info_span!("system", name = &*name).entered(); + system.run((), world); + #[cfg(feature = "trace")] + system_span.exit(); + + #[cfg(feature = "trace")] + let _apply_buffers_span = info_span!("apply_buffers", name = &*name).entered(); + system.apply_buffers(world); + } + + self.evaluated_sets.clear(); + self.completed_systems.clear(); + } +} + +impl SimpleExecutor { + pub const fn new() -> Self { + Self { + evaluated_sets: FixedBitSet::new(), + completed_systems: FixedBitSet::new(), + } + } +} + +fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut World) -> bool { + // not short-circuiting is intentional + #[allow(clippy::unnecessary_fold)] + conditions + .iter_mut() + .map(|condition| { + #[cfg(feature = "trace")] + let _condition_span = info_span!("condition", name = &*condition.name()).entered(); + condition.run((), world) + }) + .fold(true, |acc, res| acc && res) +} diff --git a/crates/bevy_ecs/src/schedule/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs new file mode 100644 index 0000000000000..34bf6d096ce0e --- /dev/null +++ b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs @@ -0,0 +1,137 @@ +#[cfg(feature = "trace")] +use bevy_utils::tracing::info_span; +use fixedbitset::FixedBitSet; + +use crate::{ + schedule::{ + is_apply_system_buffers, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule, + }, + world::World, +}; + +/// Runs the schedule using a single thread. +/// +/// Useful if you're dealing with a single-threaded environment, saving your threads for +/// other things, or just trying minimize overhead. +#[derive(Default)] +pub struct SingleThreadedExecutor { + /// System sets whose conditions have been evaluated. + evaluated_sets: FixedBitSet, + /// Systems that have run or been skipped. + completed_systems: FixedBitSet, + /// Systems that have run but have not had their buffers applied. + unapplied_systems: FixedBitSet, +} + +impl SystemExecutor for SingleThreadedExecutor { + fn kind(&self) -> ExecutorKind { + ExecutorKind::SingleThreaded + } + + fn init(&mut self, schedule: &SystemSchedule) { + // pre-allocate space + let sys_count = schedule.system_ids.len(); + let set_count = schedule.set_ids.len(); + self.evaluated_sets = FixedBitSet::with_capacity(set_count); + self.completed_systems = FixedBitSet::with_capacity(sys_count); + self.unapplied_systems = FixedBitSet::with_capacity(sys_count); + } + + fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) { + for system_index in 0..schedule.systems.len() { + #[cfg(feature = "trace")] + let name = schedule.systems[system_index].name(); + #[cfg(feature = "trace")] + let should_run_span = info_span!("check_conditions", name = &*name).entered(); + + let mut should_run = !self.completed_systems.contains(system_index); + for set_idx in schedule.sets_of_systems[system_index].ones() { + if self.evaluated_sets.contains(set_idx) { + continue; + } + + // evaluate system set's conditions + let set_conditions_met = + evaluate_and_fold_conditions(&mut schedule.set_conditions[set_idx], world); + + if !set_conditions_met { + self.completed_systems + .union_with(&schedule.systems_in_sets[set_idx]); + } + + should_run &= set_conditions_met; + self.evaluated_sets.insert(set_idx); + } + + // evaluate system's conditions + let system_conditions_met = + evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world); + + should_run &= system_conditions_met; + + #[cfg(feature = "trace")] + should_run_span.exit(); + + // system has either been skipped or will run + self.completed_systems.insert(system_index); + + if !should_run { + continue; + } + + let system = &mut schedule.systems[system_index]; + if is_apply_system_buffers(system) { + #[cfg(feature = "trace")] + let system_span = info_span!("system", name = &*name).entered(); + self.apply_system_buffers(schedule, world); + #[cfg(feature = "trace")] + system_span.exit(); + } else { + #[cfg(feature = "trace")] + let system_span = info_span!("system", name = &*name).entered(); + system.run((), world); + #[cfg(feature = "trace")] + system_span.exit(); + self.unapplied_systems.insert(system_index); + } + } + + self.apply_system_buffers(schedule, world); + self.evaluated_sets.clear(); + self.completed_systems.clear(); + } +} + +impl SingleThreadedExecutor { + pub const fn new() -> Self { + Self { + evaluated_sets: FixedBitSet::new(), + completed_systems: FixedBitSet::new(), + unapplied_systems: FixedBitSet::new(), + } + } + + fn apply_system_buffers(&mut self, schedule: &mut SystemSchedule, world: &mut World) { + for system_index in self.unapplied_systems.ones() { + let system = &mut schedule.systems[system_index]; + #[cfg(feature = "trace")] + let _apply_buffers_span = info_span!("apply_buffers", name = &*system.name()).entered(); + system.apply_buffers(world); + } + + self.unapplied_systems.clear(); + } +} + +fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut World) -> bool { + // not short-circuiting is intentional + #[allow(clippy::unnecessary_fold)] + conditions + .iter_mut() + .map(|condition| { + #[cfg(feature = "trace")] + let _condition_span = info_span!("condition", name = &*condition.name()).entered(); + condition.run((), world) + }) + .fold(true, |acc, res| acc && res) +} diff --git a/crates/bevy_ecs/src/schedule/graph_utils.rs b/crates/bevy_ecs/src/schedule/graph_utils.rs new file mode 100644 index 0000000000000..da6bb58c88f08 --- /dev/null +++ b/crates/bevy_ecs/src/schedule/graph_utils.rs @@ -0,0 +1,233 @@ +use std::fmt::Debug; + +use bevy_utils::{ + petgraph::{graphmap::NodeTrait, prelude::*}, + HashMap, HashSet, +}; +use fixedbitset::FixedBitSet; + +use crate::schedule::set::*; + +/// Unique identifier for a system or system set. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) enum NodeId { + System(usize), + Set(usize), +} + +impl NodeId { + /// Returns the internal integer value. + pub fn index(&self) -> usize { + match self { + NodeId::System(index) | NodeId::Set(index) => *index, + } + } + + /// Returns `true` if the identified node is a system. + pub const fn is_system(&self) -> bool { + matches!(self, NodeId::System(_)) + } + + /// Returns `true` if the identified node is a system set. + pub const fn is_set(&self) -> bool { + matches!(self, NodeId::Set(_)) + } +} + +/// Specifies what kind of edge should be added to the dependency graph. +#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub(crate) enum DependencyKind { + /// A node that should be preceded. + Before, + /// A node that should be succeeded. + After, +} + +/// An edge to be added to the dependency graph. +#[derive(Clone)] +pub(crate) struct Dependency { + pub(crate) kind: DependencyKind, + pub(crate) set: BoxedSystemSet, +} + +impl Dependency { + pub fn new(kind: DependencyKind, set: BoxedSystemSet) -> Self { + Self { kind, set } + } +} + +/// Configures ambiguity detection for a single system. +#[derive(Clone, Debug, Default)] +pub(crate) enum Ambiguity { + #[default] + Check, + /// Ignore warnings with systems in any of these system sets. May contain duplicates. + IgnoreWithSet(Vec), + /// Ignore all warnings. + IgnoreAll, +} + +#[derive(Clone)] +pub(crate) struct GraphInfo { + pub(crate) sets: Vec, + pub(crate) dependencies: Vec, + pub(crate) ambiguous_with: Ambiguity, +} + +/// Converts 2D row-major pair of indices into a 1D array index. +pub(crate) fn index(row: usize, col: usize, num_cols: usize) -> usize { + debug_assert!(col < num_cols); + (row * num_cols) + col +} + +/// Converts a 1D array index into a 2D row-major pair of indices. +pub(crate) fn row_col(index: usize, num_cols: usize) -> (usize, usize) { + (index / num_cols, index % num_cols) +} + +/// Stores the results of the graph analysis. +pub(crate) struct CheckGraphResults { + /// Boolean reachability matrix for the graph. + pub(crate) reachable: FixedBitSet, + /// Pairs of nodes that have a path connecting them. + pub(crate) connected: HashSet<(V, V)>, + /// Pairs of nodes that don't have a path connecting them. + pub(crate) disconnected: HashSet<(V, V)>, + /// Edges that are redundant because a longer path exists. + pub(crate) transitive_edges: Vec<(V, V)>, + /// Variant of the graph with no transitive edges. + pub(crate) transitive_reduction: DiGraphMap, + /// Variant of the graph with all possible transitive edges. + // TODO: this will very likely be used by "if-needed" ordering + #[allow(dead_code)] + pub(crate) transitive_closure: DiGraphMap, +} + +impl Default for CheckGraphResults { + fn default() -> Self { + Self { + reachable: FixedBitSet::new(), + connected: HashSet::new(), + disconnected: HashSet::new(), + transitive_edges: Vec::new(), + transitive_reduction: DiGraphMap::new(), + transitive_closure: DiGraphMap::new(), + } + } +} + +/// Processes a DAG and computes its: +/// - transitive reduction (along with the set of removed edges) +/// - transitive closure +/// - reachability matrix (as a bitset) +/// - pairs of nodes connected by a path +/// - pairs of nodes not connected by a path +/// +/// The algorithm implemented comes from +/// ["On the calculation of transitive reduction-closure of orders"][1] by Habib, Morvan and Rampon. +/// +/// [1]: https://doi.org/10.1016/0012-365X(93)90164-O +pub(crate) fn check_graph( + graph: &DiGraphMap, + topological_order: &[V], +) -> CheckGraphResults +where + V: NodeTrait + Debug, +{ + if graph.node_count() == 0 { + return CheckGraphResults::default(); + } + + let n = graph.node_count(); + + // build a copy of the graph where the nodes and edges appear in topsorted order + let mut map = HashMap::with_capacity(n); + let mut topsorted = DiGraphMap::::new(); + // iterate nodes in topological order + for (i, &node) in topological_order.iter().enumerate() { + map.insert(node, i); + topsorted.add_node(node); + // insert nodes as successors to their predecessors + for pred in graph.neighbors_directed(node, Direction::Incoming) { + topsorted.add_edge(pred, node, ()); + } + } + + let mut reachable = FixedBitSet::with_capacity(n * n); + let mut connected = HashSet::new(); + let mut disconnected = HashSet::new(); + + let mut transitive_edges = Vec::new(); + let mut transitive_reduction = DiGraphMap::::new(); + let mut transitive_closure = DiGraphMap::::new(); + + let mut visited = FixedBitSet::with_capacity(n); + + // iterate nodes in topological order + for node in topsorted.nodes() { + transitive_reduction.add_node(node); + transitive_closure.add_node(node); + } + + // iterate nodes in reverse topological order + for a in topsorted.nodes().rev() { + let index_a = *map.get(&a).unwrap(); + // iterate their successors in topological order + for b in topsorted.neighbors_directed(a, Direction::Outgoing) { + let index_b = *map.get(&b).unwrap(); + debug_assert!(index_a < index_b); + if !visited[index_b] { + // edge is not redundant + transitive_reduction.add_edge(a, b, ()); + transitive_closure.add_edge(a, b, ()); + reachable.insert(index(index_a, index_b, n)); + + let successors = transitive_closure + .neighbors_directed(b, Direction::Outgoing) + .collect::>(); + for c in successors { + let index_c = *map.get(&c).unwrap(); + debug_assert!(index_b < index_c); + if !visited[index_c] { + visited.insert(index_c); + transitive_closure.add_edge(a, c, ()); + reachable.insert(index(index_a, index_c, n)); + } + } + } else { + // edge is redundant + transitive_edges.push((a, b)); + } + } + + visited.clear(); + } + + // partition pairs of nodes into "connected by path" and "not connected by path" + for i in 0..(n - 1) { + // reachable is upper triangular because the nodes were topsorted + for index in index(i, i + 1, n)..=index(i, n - 1, n) { + let (a, b) = row_col(index, n); + let pair = (topological_order[a], topological_order[b]); + if reachable[index] { + connected.insert(pair); + } else { + disconnected.insert(pair); + } + } + } + + // fill diagonal (nodes reach themselves) + // for i in 0..n { + // reachable.set(index(i, i, n), true); + // } + + CheckGraphResults { + reachable, + connected, + disconnected, + transitive_edges, + transitive_reduction, + transitive_closure, + } +} diff --git a/crates/bevy_ecs/src/schedule/migration.rs b/crates/bevy_ecs/src/schedule/migration.rs new file mode 100644 index 0000000000000..9c2dddbd0cf95 --- /dev/null +++ b/crates/bevy_ecs/src/schedule/migration.rs @@ -0,0 +1,38 @@ +use crate::schedule::*; +use crate::world::World; + +/// Temporary "stageless" `App` methods. +pub trait AppExt { + /// Sets the [`Schedule`] that will be modified by default when you call `App::add_system` + /// and similar methods. + /// + /// **Note:** This will create the schedule if it does not already exist. + fn set_default_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self; + /// Applies the function to the [`Schedule`] associated with `label`. + /// + /// **Note:** This will create the schedule if it does not already exist. + fn edit_schedule( + &mut self, + label: impl ScheduleLabel, + f: impl FnMut(&mut Schedule), + ) -> &mut Self; + /// Adds [`State`] and [`NextState`] resources, [`OnEnter`] and [`OnExit`] schedules + /// for each state variant, and an instance of [`apply_state_transition::`] in + /// \ so that transitions happen before `Update`. + fn add_state(&mut self) -> &mut Self; +} + +/// Temporary "stageless" [`World`] methods. +pub trait WorldExt { + /// Runs the [`Schedule`] associated with `label`. + fn run_schedule(&mut self, label: impl ScheduleLabel); +} + +impl WorldExt for World { + fn run_schedule(&mut self, label: impl ScheduleLabel) { + if let Some(mut schedule) = self.resource_mut::().remove(&label) { + schedule.run(self); + self.resource_mut::().insert(label, schedule); + } + } +} diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs new file mode 100644 index 0000000000000..77690bd29f9b4 --- /dev/null +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -0,0 +1,622 @@ +mod condition; +mod config; +mod executor; +mod graph_utils; +mod migration; +mod schedule; +mod set; +mod state; + +pub use self::condition::*; +pub use self::config::*; +pub use self::executor::*; +use self::graph_utils::*; +pub use self::migration::*; +pub use self::schedule::*; +pub use self::set::*; +pub use self::state::*; + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::{AtomicU32, Ordering}; + + pub use crate as bevy_ecs; + pub use crate::schedule::{IntoSystemConfig, IntoSystemSetConfig, Schedule, SystemSet}; + pub use crate::system::{Res, ResMut}; + pub use crate::{prelude::World, system::Resource}; + + #[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] + enum TestSet { + A, + B, + C, + D, + X, + } + + #[derive(Resource, Default)] + struct SystemOrder(Vec); + + #[derive(Resource, Default)] + struct RunConditionBool(pub bool); + + #[derive(Resource, Default)] + struct Counter(pub AtomicU32); + + fn make_exclusive_system(tag: u32) -> impl FnMut(&mut World) { + move |world| world.resource_mut::().0.push(tag) + } + + fn make_function_system(tag: u32) -> impl FnMut(ResMut) { + move |mut resource: ResMut| resource.0.push(tag) + } + + fn named_system(mut resource: ResMut) { + resource.0.push(u32::MAX); + } + + fn named_exclusive_system(world: &mut World) { + world.resource_mut::().0.push(u32::MAX); + } + + fn counting_system(counter: Res) { + counter.0.fetch_add(1, Ordering::Relaxed); + } + + mod system_execution { + use super::*; + + #[test] + fn run_system() { + let mut world = World::default(); + let mut schedule = Schedule::default(); + + world.init_resource::(); + + schedule.add_system(make_function_system(0)); + schedule.run(&mut world); + + assert_eq!(world.resource::().0, vec![0]); + } + + #[test] + fn run_exclusive_system() { + let mut world = World::default(); + let mut schedule = Schedule::default(); + + world.init_resource::(); + + schedule.add_system(make_exclusive_system(0)); + schedule.run(&mut world); + + assert_eq!(world.resource::().0, vec![0]); + } + + #[test] + #[cfg(not(miri))] + fn parallel_execution() { + use bevy_tasks::{ComputeTaskPool, TaskPool}; + use std::sync::{Arc, Barrier}; + + let mut world = World::default(); + let mut schedule = Schedule::default(); + let thread_count = ComputeTaskPool::init(TaskPool::default).thread_num(); + + let barrier = Arc::new(Barrier::new(thread_count)); + + for _ in 0..thread_count { + let inner = barrier.clone(); + schedule.add_system(move || { + inner.wait(); + }); + } + + schedule.run(&mut world); + } + } + + mod system_ordering { + use super::*; + + #[test] + fn order_systems() { + let mut world = World::default(); + let mut schedule = Schedule::default(); + + world.init_resource::(); + + schedule.add_system(named_system); + schedule.add_system(make_function_system(1).before(named_system)); + schedule.add_system( + make_function_system(0) + .after(named_system) + .in_set(TestSet::A), + ); + schedule.run(&mut world); + + assert_eq!(world.resource::().0, vec![1, u32::MAX, 0]); + + world.insert_resource(SystemOrder::default()); + + assert_eq!(world.resource::().0, vec![]); + + // modify the schedule after it's been initialized and test ordering with sets + schedule.configure_set(TestSet::A.after(named_system)); + schedule.add_system( + make_function_system(3) + .before(TestSet::A) + .after(named_system), + ); + schedule.add_system(make_function_system(4).after(TestSet::A)); + schedule.run(&mut world); + + assert_eq!( + world.resource::().0, + vec![1, u32::MAX, 3, 0, 4] + ); + } + + #[test] + fn order_exclusive_systems() { + let mut world = World::default(); + let mut schedule = Schedule::default(); + + world.init_resource::(); + + schedule.add_system(named_exclusive_system); + schedule.add_system(make_exclusive_system(1).before(named_exclusive_system)); + schedule.add_system(make_exclusive_system(0).after(named_exclusive_system)); + schedule.run(&mut world); + + assert_eq!(world.resource::().0, vec![1, u32::MAX, 0]); + } + + #[test] + fn add_systems_correct_order() { + #[derive(Resource)] + struct X(Vec); + + let mut world = World::new(); + world.init_resource::(); + + let mut schedule = Schedule::new(); + schedule.add_systems( + ( + make_function_system(0), + make_function_system(1), + make_exclusive_system(2), + make_function_system(3), + ) + .chain(), + ); + + schedule.run(&mut world); + assert_eq!(world.resource::().0, vec![0, 1, 2, 3]); + } + } + + mod conditions { + use crate::change_detection::DetectChanges; + + use super::*; + + #[test] + fn system_with_condition() { + let mut world = World::default(); + let mut schedule = Schedule::default(); + + world.init_resource::(); + world.init_resource::(); + + schedule.add_system( + make_function_system(0).run_if(|condition: Res| condition.0), + ); + + schedule.run(&mut world); + assert_eq!(world.resource::().0, vec![]); + + world.resource_mut::().0 = true; + schedule.run(&mut world); + assert_eq!(world.resource::().0, vec![0]); + } + + #[test] + fn run_exclusive_system_with_condition() { + let mut world = World::default(); + let mut schedule = Schedule::default(); + + world.init_resource::(); + world.init_resource::(); + + schedule.add_system( + make_exclusive_system(0).run_if(|condition: Res| condition.0), + ); + + schedule.run(&mut world); + assert_eq!(world.resource::().0, vec![]); + + world.resource_mut::().0 = true; + schedule.run(&mut world); + assert_eq!(world.resource::().0, vec![0]); + } + + #[test] + fn multiple_conditions_on_system() { + let mut world = World::default(); + let mut schedule = Schedule::default(); + + world.init_resource::(); + + schedule.add_system(counting_system.run_if(|| false).run_if(|| false)); + schedule.add_system(counting_system.run_if(|| true).run_if(|| false)); + schedule.add_system(counting_system.run_if(|| false).run_if(|| true)); + schedule.add_system(counting_system.run_if(|| true).run_if(|| true)); + + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + } + + #[test] + fn multiple_conditions_on_system_sets() { + let mut world = World::default(); + let mut schedule = Schedule::default(); + + world.init_resource::(); + + schedule.configure_set(TestSet::A.run_if(|| false).run_if(|| false)); + schedule.add_system(counting_system.in_set(TestSet::A)); + schedule.configure_set(TestSet::B.run_if(|| true).run_if(|| false)); + schedule.add_system(counting_system.in_set(TestSet::B)); + schedule.configure_set(TestSet::C.run_if(|| false).run_if(|| true)); + schedule.add_system(counting_system.in_set(TestSet::C)); + schedule.configure_set(TestSet::D.run_if(|| true).run_if(|| true)); + schedule.add_system(counting_system.in_set(TestSet::D)); + + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + } + + #[test] + fn systems_nested_in_system_sets() { + let mut world = World::default(); + let mut schedule = Schedule::default(); + + world.init_resource::(); + + schedule.configure_set(TestSet::A.run_if(|| false)); + schedule.add_system(counting_system.in_set(TestSet::A).run_if(|| false)); + schedule.configure_set(TestSet::B.run_if(|| true)); + schedule.add_system(counting_system.in_set(TestSet::B).run_if(|| false)); + schedule.configure_set(TestSet::C.run_if(|| false)); + schedule.add_system(counting_system.in_set(TestSet::C).run_if(|| true)); + schedule.configure_set(TestSet::D.run_if(|| true)); + schedule.add_system(counting_system.in_set(TestSet::D).run_if(|| true)); + + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + } + + #[test] + fn system_conditions_and_change_detection() { + #[derive(Resource, Default)] + struct Bool2(pub bool); + + let mut world = World::default(); + world.init_resource::(); + world.init_resource::(); + world.init_resource::(); + let mut schedule = Schedule::default(); + + schedule.add_system( + counting_system + .run_if(|res1: Res| res1.is_changed()) + .run_if(|res2: Res| res2.is_changed()), + ); + + // both resource were just added. + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + + // nothing has changed + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + + // RunConditionBool has changed, but counting_system did not run + world.get_resource_mut::().unwrap().0 = false; + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + + // internal state for the bool2 run criteria was updated in the + // previous run, so system still does not run + world.get_resource_mut::().unwrap().0 = false; + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + + // internal state for bool2 was updated, so system still does not run + world.get_resource_mut::().unwrap().0 = false; + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + + // now check that it works correctly changing Bool2 first and then RunConditionBool + world.get_resource_mut::().unwrap().0 = false; + world.get_resource_mut::().unwrap().0 = false; + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 2); + } + + #[test] + fn system_set_conditions_and_change_detection() { + #[derive(Resource, Default)] + struct Bool2(pub bool); + + let mut world = World::default(); + world.init_resource::(); + world.init_resource::(); + world.init_resource::(); + let mut schedule = Schedule::default(); + + schedule.configure_set( + TestSet::A + .run_if(|res1: Res| res1.is_changed()) + .run_if(|res2: Res| res2.is_changed()), + ); + + schedule.add_system(counting_system.in_set(TestSet::A)); + + // both resource were just added. + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + + // nothing has changed + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + + // RunConditionBool has changed, but counting_system did not run + world.get_resource_mut::().unwrap().0 = false; + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + + // internal state for the bool2 run criteria was updated in the + // previous run, so system still does not run + world.get_resource_mut::().unwrap().0 = false; + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + + // internal state for bool2 was updated, so system still does not run + world.get_resource_mut::().unwrap().0 = false; + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + + // the system only runs when both are changed on the same run + world.get_resource_mut::().unwrap().0 = false; + world.get_resource_mut::().unwrap().0 = false; + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 2); + } + + #[test] + fn mixed_conditions_and_change_detection() { + #[derive(Resource, Default)] + struct Bool2(pub bool); + + let mut world = World::default(); + world.init_resource::(); + world.init_resource::(); + world.init_resource::(); + let mut schedule = Schedule::default(); + + schedule + .configure_set(TestSet::A.run_if(|res1: Res| res1.is_changed())); + + schedule.add_system( + counting_system + .run_if(|res2: Res| res2.is_changed()) + .in_set(TestSet::A), + ); + + // both resource were just added. + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + + // nothing has changed + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + + // RunConditionBool has changed, but counting_system did not run + world.get_resource_mut::().unwrap().0 = false; + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + + // we now only change bool2 and the system also should not run + world.get_resource_mut::().unwrap().0 = false; + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + + // internal state for the bool2 run criteria was updated in the + // previous run, so system still does not run + world.get_resource_mut::().unwrap().0 = false; + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + + // the system only runs when both are changed on the same run + world.get_resource_mut::().unwrap().0 = false; + world.get_resource_mut::().unwrap().0 = false; + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 2); + } + } + + mod schedule_build_errors { + use super::*; + + #[test] + #[should_panic] + fn dependency_loop() { + let mut schedule = Schedule::new(); + schedule.configure_set(TestSet::X.after(TestSet::X)); + } + + #[test] + fn dependency_cycle() { + let mut world = World::new(); + let mut schedule = Schedule::new(); + + schedule.configure_set(TestSet::A.after(TestSet::B)); + schedule.configure_set(TestSet::B.after(TestSet::A)); + + let result = schedule.initialize(&mut world); + assert!(matches!(result, Err(ScheduleBuildError::DependencyCycle))); + + fn foo() {} + fn bar() {} + + let mut world = World::new(); + let mut schedule = Schedule::new(); + + schedule.add_systems((foo.after(bar), bar.after(foo))); + let result = schedule.initialize(&mut world); + assert!(matches!(result, Err(ScheduleBuildError::DependencyCycle))); + } + + #[test] + #[should_panic] + fn hierarchy_loop() { + let mut schedule = Schedule::new(); + schedule.configure_set(TestSet::X.in_set(TestSet::X)); + } + + #[test] + fn hierarchy_cycle() { + let mut world = World::new(); + let mut schedule = Schedule::new(); + + schedule.configure_set(TestSet::A.in_set(TestSet::B)); + schedule.configure_set(TestSet::B.in_set(TestSet::A)); + + let result = schedule.initialize(&mut world); + assert!(matches!(result, Err(ScheduleBuildError::HierarchyCycle))); + } + + #[test] + fn system_type_set_ambiguity() { + // Define some systems. + fn foo() {} + fn bar() {} + + let mut world = World::new(); + let mut schedule = Schedule::new(); + + // Schedule `bar` to run after `foo`. + schedule.add_system(foo); + schedule.add_system(bar.after(foo)); + + // There's only one `foo`, so it's fine. + let result = schedule.initialize(&mut world); + assert!(result.is_ok()); + + // Schedule another `foo`. + schedule.add_system(foo); + + // When there are multiple instances of `foo`, dependencies on + // `foo` are no longer allowed. Too much ambiguity. + let result = schedule.initialize(&mut world); + assert!(matches!( + result, + Err(ScheduleBuildError::SystemTypeSetAmbiguity(_)) + )); + + // same goes for `ambiguous_with` + let mut schedule = Schedule::new(); + schedule.add_system(foo); + schedule.add_system(bar.ambiguous_with(foo)); + let result = schedule.initialize(&mut world); + assert!(result.is_ok()); + schedule.add_system(foo); + let result = schedule.initialize(&mut world); + assert!(matches!( + result, + Err(ScheduleBuildError::SystemTypeSetAmbiguity(_)) + )); + } + + #[test] + #[should_panic] + fn in_system_type_set() { + fn foo() {} + fn bar() {} + + let mut schedule = Schedule::new(); + schedule.add_system(foo.in_set(bar.into_system_set())); + } + + #[test] + #[should_panic] + fn configure_system_type_set() { + fn foo() {} + let mut schedule = Schedule::new(); + schedule.configure_set(foo.into_system_set()); + } + + #[test] + fn hierarchy_redundancy() { + let mut world = World::new(); + let mut schedule = Schedule::new(); + + schedule.set_build_settings( + ScheduleBuildSettings::new().with_hierarchy_detection(LogLevel::Error), + ); + + // Add `A`. + schedule.configure_set(TestSet::A); + + // Add `B` as child of `A`. + schedule.configure_set(TestSet::B.in_set(TestSet::A)); + + // Add `X` as child of both `A` and `B`. + schedule.configure_set(TestSet::X.in_set(TestSet::A).in_set(TestSet::B)); + + // `X` cannot be the `A`'s child and grandchild at the same time. + let result = schedule.initialize(&mut world); + assert!(matches!( + result, + Err(ScheduleBuildError::HierarchyRedundancy) + )); + } + + #[test] + fn cross_dependency() { + let mut world = World::new(); + let mut schedule = Schedule::new(); + + // Add `B` and give it both kinds of relationships with `A`. + schedule.configure_set(TestSet::B.in_set(TestSet::A)); + schedule.configure_set(TestSet::B.after(TestSet::A)); + let result = schedule.initialize(&mut world); + assert!(matches!( + result, + Err(ScheduleBuildError::CrossDependency(_, _)) + )); + } + + #[test] + fn ambiguity() { + #[derive(Resource)] + struct X; + + fn res_ref(_x: Res) {} + fn res_mut(_x: ResMut) {} + + let mut world = World::new(); + let mut schedule = Schedule::new(); + + schedule.set_build_settings( + ScheduleBuildSettings::new().with_ambiguity_detection(LogLevel::Error), + ); + + schedule.add_systems((res_ref, res_mut)); + let result = schedule.initialize(&mut world); + assert!(matches!(result, Err(ScheduleBuildError::Ambiguity))); + } + } +} diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs new file mode 100644 index 0000000000000..fc1b2f0ad71ba --- /dev/null +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -0,0 +1,1099 @@ +use std::{ + fmt::{Debug, Write}, + result::Result, +}; + +use bevy_utils::default; +#[cfg(feature = "trace")] +use bevy_utils::tracing::info_span; +use bevy_utils::{ + petgraph::{algo::tarjan_scc, prelude::*}, + thiserror::Error, + tracing::{error, info, warn}, + HashMap, HashSet, +}; + +use fixedbitset::FixedBitSet; + +use crate::{ + self as bevy_ecs, + component::ComponentId, + schedule::*, + system::{BoxedSystem, Resource}, + world::World, +}; + +/// Resource that stores [`Schedule`]s mapped to [`ScheduleLabel`]s. +#[derive(Default, Resource)] +pub struct Schedules { + inner: HashMap, +} + +impl Schedules { + /// Constructs an empty `Schedules` with zero initial capacity. + pub fn new() -> Self { + Self { + inner: HashMap::new(), + } + } + + /// Inserts a labeled schedule into the map. + /// + /// If the map already had an entry for `label`, `schedule` is inserted, + /// and the old schedule is returned. Otherwise, `None` is returned. + pub fn insert(&mut self, label: impl ScheduleLabel, schedule: Schedule) -> Option { + let label: Box = Box::new(label); + if self.inner.contains_key(&label) { + warn!("schedule with label {:?} already exists", label); + } + self.inner.insert(label, schedule) + } + + /// Removes the schedule corresponding to the `label` from the map, returning it if it existed. + pub fn remove(&mut self, label: &dyn ScheduleLabel) -> Option { + if !self.inner.contains_key(label) { + warn!("schedule with label {:?} not found", label); + } + self.inner.remove(label) + } + + /// Returns a reference to the schedule associated with `label`, if it exists. + pub fn get(&self, label: &dyn ScheduleLabel) -> Option<&Schedule> { + self.inner.get(label) + } + + /// Returns a mutable reference to the schedule associated with `label`, if it exists. + pub fn get_mut(&mut self, label: &dyn ScheduleLabel) -> Option<&mut Schedule> { + self.inner.get_mut(label) + } + + /// Iterates the change ticks of all systems in all stored schedules and clamps any older than + /// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE). + /// This prevents overflow and thus prevents false positives. + pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { + #[cfg(feature = "trace")] + let _all_span = info_span!("check stored schedule ticks").entered(); + // label used when trace feature is enabled + #[allow(unused_variables)] + for (label, schedule) in self.inner.iter_mut() { + #[cfg(feature = "trace")] + let name = format!("{:?}", label); + #[cfg(feature = "trace")] + let _one_span = info_span!("check schedule ticks", name = &name).entered(); + schedule.check_change_ticks(change_tick); + } + } +} + +/// A collection of systems, and the metadata and executor needed to run them +/// in a certain order under certain conditions. +pub struct Schedule { + graph: ScheduleGraph, + executable: SystemSchedule, + executor: Box, + executor_initialized: bool, +} + +impl Default for Schedule { + fn default() -> Self { + Self::new() + } +} + +impl Schedule { + /// Constructs an empty `Schedule`. + pub fn new() -> Self { + Self { + graph: ScheduleGraph::new(), + executable: SystemSchedule::new(), + executor: Box::new(MultiThreadedExecutor::new()), + executor_initialized: false, + } + } + + /// Add a system to the schedule. + pub fn add_system

(&mut self, system: impl IntoSystemConfig

) -> &mut Self { + self.graph.add_system(system); + self + } + + /// Add a collection of systems to the schedule. + pub fn add_systems

(&mut self, systems: impl IntoSystemConfigs

) -> &mut Self { + self.graph.add_systems(systems); + self + } + + /// Configure a system set in this schedule. + pub fn configure_set(&mut self, set: impl IntoSystemSetConfig) -> &mut Self { + self.graph.configure_set(set); + self + } + + /// Configure a collection of system sets in this schedule. + pub fn configure_sets(&mut self, sets: impl IntoSystemSetConfigs) -> &mut Self { + self.graph.configure_sets(sets); + self + } + + /// Changes the system set that new systems and system sets will join by default + /// if they aren't already part of one. + pub fn set_default_set(&mut self, set: impl SystemSet) -> &mut Self { + self.graph.set_default_set(set); + self + } + + /// Changes miscellaneous build settings. + pub fn set_build_settings(&mut self, settings: ScheduleBuildSettings) -> &mut Self { + self.graph.settings = settings; + self + } + + /// Returns the schedule's current execution strategy. + pub fn get_executor_kind(&self) -> ExecutorKind { + self.executor.kind() + } + + /// Sets the schedule's execution strategy. + pub fn set_executor_kind(&mut self, executor: ExecutorKind) -> &mut Self { + if executor != self.executor.kind() { + self.executor = match executor { + ExecutorKind::Simple => Box::new(SimpleExecutor::new()), + ExecutorKind::SingleThreaded => Box::new(SingleThreadedExecutor::new()), + ExecutorKind::MultiThreaded => Box::new(MultiThreadedExecutor::new()), + }; + self.executor_initialized = false; + } + self + } + + /// Runs all systems in this schedule on the `world`, using its current execution strategy. + pub fn run(&mut self, world: &mut World) { + world.check_change_ticks(); + self.initialize(world).unwrap(); + // TODO: label + #[cfg(feature = "trace")] + let _span = info_span!("schedule").entered(); + self.executor.run(&mut self.executable, world); + } + + /// Initializes any newly-added systems and conditions, rebuilds the executable schedule, + /// and re-initializes the executor. + pub fn initialize(&mut self, world: &mut World) -> Result<(), ScheduleBuildError> { + if self.graph.changed { + self.graph.initialize(world); + self.graph.update_schedule(&mut self.executable)?; + self.graph.changed = false; + self.executor_initialized = false; + } + + if !self.executor_initialized { + self.executor.init(&self.executable); + self.executor_initialized = true; + } + + Ok(()) + } + + /// Iterates the change ticks of all systems in the schedule and clamps any older than + /// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE). + /// This prevents overflow and thus prevents false positives. + pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { + for system in &mut self.executable.systems { + system.check_change_tick(change_tick); + } + + for conditions in &mut self.executable.system_conditions { + for system in conditions.iter_mut() { + system.check_change_tick(change_tick); + } + } + + for conditions in &mut self.executable.set_conditions { + for system in conditions.iter_mut() { + system.check_change_tick(change_tick); + } + } + } +} + +/// A directed acylic graph structure. +#[derive(Default)] +struct Dag { + /// A directed graph. + graph: DiGraphMap, + /// A cached topological ordering of the graph. + topsort: Vec, +} + +impl Dag { + fn new() -> Self { + Self { + graph: DiGraphMap::new(), + topsort: Vec::new(), + } + } +} + +/// A [`SystemSet`] with metadata, stored in a [`ScheduleGraph`]. +struct SystemSetNode { + inner: BoxedSystemSet, + /// `true` if this system set was modified with `configure_set` + configured: bool, +} + +impl SystemSetNode { + pub fn new(set: BoxedSystemSet) -> Self { + Self { + inner: set, + configured: false, + } + } + + pub fn name(&self) -> String { + format!("{:?}", &self.inner) + } + + pub fn is_system_type(&self) -> bool { + self.inner.is_system_type() + } +} + +/// Metadata for a [`Schedule`]. +#[derive(Default)] +struct ScheduleGraph { + systems: Vec>, + system_conditions: Vec>>, + system_sets: Vec, + system_set_conditions: Vec>>, + system_set_ids: HashMap, + uninit: Vec<(NodeId, usize)>, + hierarchy: Dag, + dependency: Dag, + dependency_flattened: Dag, + ambiguous_with: UnGraphMap, + ambiguous_with_flattened: UnGraphMap, + ambiguous_with_all: HashSet, + default_set: Option, + changed: bool, + settings: ScheduleBuildSettings, +} + +impl ScheduleGraph { + pub fn new() -> Self { + Self { + systems: Vec::new(), + system_conditions: Vec::new(), + system_sets: Vec::new(), + system_set_conditions: Vec::new(), + system_set_ids: HashMap::new(), + uninit: Vec::new(), + hierarchy: Dag::new(), + dependency: Dag::new(), + dependency_flattened: Dag::new(), + ambiguous_with: UnGraphMap::new(), + ambiguous_with_flattened: UnGraphMap::new(), + ambiguous_with_all: HashSet::new(), + default_set: None, + changed: false, + settings: default(), + } + } + + fn set_default_set(&mut self, set: impl SystemSet) { + assert!( + !set.is_system_type(), + "adding arbitrary systems to a system type set is not allowed" + ); + self.default_set = Some(Box::new(set)); + } + + fn add_systems

(&mut self, systems: impl IntoSystemConfigs

) { + let SystemConfigs { systems, chained } = systems.into_configs(); + let mut system_iter = systems.into_iter(); + if chained { + let Some(prev) = system_iter.next() else { return }; + let mut prev_id = self.add_system_inner(prev).unwrap(); + for next in system_iter { + let next_id = self.add_system_inner(next).unwrap(); + self.dependency.graph.add_edge(prev_id, next_id, ()); + prev_id = next_id; + } + } else { + for system in system_iter { + self.add_system_inner(system).unwrap(); + } + } + } + + fn add_system

(&mut self, system: impl IntoSystemConfig

) { + self.add_system_inner(system).unwrap(); + } + + fn add_system_inner

( + &mut self, + system: impl IntoSystemConfig

, + ) -> Result { + let SystemConfig { + system, + mut graph_info, + conditions, + } = system.into_config(); + + let id = NodeId::System(self.systems.len()); + + if graph_info.sets.is_empty() { + if let Some(default) = self.default_set.as_ref() { + graph_info.sets.push(default.dyn_clone()); + } + } + + // graph updates are immediate + self.update_graphs(id, graph_info)?; + + // system init has to be deferred (need `&mut World`) + self.uninit.push((id, 0)); + self.systems.push(Some(system)); + self.system_conditions.push(Some(conditions)); + + Ok(id) + } + + fn configure_sets(&mut self, sets: impl IntoSystemSetConfigs) { + let SystemSetConfigs { sets, chained } = sets.into_configs(); + let mut set_iter = sets.into_iter(); + if chained { + let Some(prev) = set_iter.next() else { return }; + let mut prev_id = self.configure_set_inner(prev).unwrap(); + for next in set_iter { + let next_id = self.configure_set_inner(next).unwrap(); + self.dependency.graph.add_edge(prev_id, next_id, ()); + prev_id = next_id; + } + } else { + for set in set_iter { + self.configure_set_inner(set).unwrap(); + } + } + } + + fn configure_set(&mut self, set: impl IntoSystemSetConfig) { + self.configure_set_inner(set).unwrap(); + } + + fn configure_set_inner( + &mut self, + set: impl IntoSystemSetConfig, + ) -> Result { + let SystemSetConfig { + set, + mut graph_info, + mut conditions, + } = set.into_config(); + + let id = match self.system_set_ids.get(&set) { + Some(&id) => id, + None => self.add_set(set.dyn_clone()), + }; + + let meta = &mut self.system_sets[id.index()]; + let already_configured = std::mem::replace(&mut meta.configured, true); + + // a system set can be configured multiple times, so this "default check" + // should only happen the first time `configure_set` is called on it + if !already_configured && graph_info.sets.is_empty() { + if let Some(default) = self.default_set.as_ref() { + info!("adding system set `{:?}` to default: `{:?}`", set, default); + graph_info.sets.push(default.dyn_clone()); + } + } + + // graph updates are immediate + self.update_graphs(id, graph_info)?; + + // system init has to be deferred (need `&mut World`) + let system_set_conditions = + self.system_set_conditions[id.index()].get_or_insert_with(Vec::new); + self.uninit.push((id, system_set_conditions.len())); + system_set_conditions.append(&mut conditions); + + Ok(id) + } + + fn add_set(&mut self, set: BoxedSystemSet) -> NodeId { + let id = NodeId::Set(self.system_sets.len()); + self.system_sets.push(SystemSetNode::new(set.dyn_clone())); + self.system_set_conditions.push(None); + self.system_set_ids.insert(set, id); + id + } + + fn check_sets( + &mut self, + id: &NodeId, + graph_info: &GraphInfo, + ) -> Result<(), ScheduleBuildError> { + for set in &graph_info.sets { + match self.system_set_ids.get(set) { + Some(set_id) => { + if id == set_id { + return Err(ScheduleBuildError::HierarchyLoop(set.dyn_clone())); + } + } + None => { + self.add_set(set.dyn_clone()); + } + } + } + + Ok(()) + } + + fn check_edges( + &mut self, + id: &NodeId, + graph_info: &GraphInfo, + ) -> Result<(), ScheduleBuildError> { + for Dependency { kind: _, set } in &graph_info.dependencies { + match self.system_set_ids.get(set) { + Some(set_id) => { + if id == set_id { + return Err(ScheduleBuildError::DependencyLoop(set.dyn_clone())); + } + } + None => { + self.add_set(set.dyn_clone()); + } + } + } + + if let Ambiguity::IgnoreWithSet(ambiguous_with) = &graph_info.ambiguous_with { + for set in ambiguous_with { + if !self.system_set_ids.contains_key(set) { + self.add_set(set.dyn_clone()); + } + } + } + + Ok(()) + } + + fn update_graphs( + &mut self, + id: NodeId, + graph_info: GraphInfo, + ) -> Result<(), ScheduleBuildError> { + self.check_sets(&id, &graph_info)?; + self.check_edges(&id, &graph_info)?; + self.changed = true; + + let GraphInfo { + sets, + dependencies, + ambiguous_with, + } = graph_info; + + if !self.hierarchy.graph.contains_node(id) { + self.hierarchy.graph.add_node(id); + } + + for set in sets.into_iter().map(|set| self.system_set_ids[&set]) { + self.hierarchy.graph.add_edge(set, id, ()); + } + + if !self.dependency.graph.contains_node(id) { + self.dependency.graph.add_node(id); + } + + for (kind, set) in dependencies + .into_iter() + .map(|Dependency { kind, set }| (kind, self.system_set_ids[&set])) + { + let (lhs, rhs) = match kind { + DependencyKind::Before => (id, set), + DependencyKind::After => (set, id), + }; + self.dependency.graph.add_edge(lhs, rhs, ()); + } + + match ambiguous_with { + Ambiguity::Check => (), + Ambiguity::IgnoreWithSet(ambigous_with) => { + for set in ambigous_with + .into_iter() + .map(|set| self.system_set_ids[&set]) + { + self.ambiguous_with.add_edge(id, set, ()); + } + } + Ambiguity::IgnoreAll => { + self.ambiguous_with_all.insert(id); + } + } + + Ok(()) + } + + fn initialize(&mut self, world: &mut World) { + for (id, i) in self.uninit.drain(..) { + match id { + NodeId::System(index) => { + self.systems[index].as_mut().unwrap().initialize(world); + if let Some(v) = self.system_conditions[index].as_mut() { + for condition in v.iter_mut() { + condition.initialize(world); + } + } + } + NodeId::Set(index) => { + if let Some(v) = self.system_set_conditions[index].as_mut() { + for condition in v.iter_mut().skip(i) { + condition.initialize(world); + } + } + } + } + } + } + + fn build_schedule(&mut self) -> Result { + // check hierarchy for cycles + let hier_scc = tarjan_scc(&self.hierarchy.graph); + if self.contains_cycles(&hier_scc) { + self.report_cycles(&hier_scc); + return Err(ScheduleBuildError::HierarchyCycle); + } + + self.hierarchy.topsort = hier_scc.into_iter().flatten().rev().collect::>(); + + let hier_results = check_graph(&self.hierarchy.graph, &self.hierarchy.topsort); + if self.contains_hierarchy_conflicts(&hier_results.transitive_edges) { + self.report_hierarchy_conflicts(&hier_results.transitive_edges); + if matches!(self.settings.hierarchy_detection, LogLevel::Error) { + return Err(ScheduleBuildError::HierarchyRedundancy); + } + } + + // remove redundant edges + self.hierarchy.graph = hier_results.transitive_reduction; + + // check dependencies for cycles + let dep_scc = tarjan_scc(&self.dependency.graph); + if self.contains_cycles(&dep_scc) { + self.report_cycles(&dep_scc); + return Err(ScheduleBuildError::DependencyCycle); + } + + self.dependency.topsort = dep_scc.into_iter().flatten().rev().collect::>(); + + // nodes can have dependent XOR hierarchical relationship + let dep_results = check_graph(&self.dependency.graph, &self.dependency.topsort); + for &(a, b) in dep_results.connected.iter() { + if hier_results.connected.contains(&(a, b)) || hier_results.connected.contains(&(b, a)) + { + let name_a = self.get_node_name(&a); + let name_b = self.get_node_name(&b); + return Err(ScheduleBuildError::CrossDependency(name_a, name_b)); + } + } + + // map system sets to all their member systems + let mut systems_in_sets = HashMap::with_capacity(self.system_sets.len()); + // iterate in reverse topological order (bottom-up) + for &id in self.hierarchy.topsort.iter().rev() { + if id.is_system() { + continue; + } + + let set = id; + systems_in_sets.insert(set, Vec::new()); + + for child in self + .hierarchy + .graph + .neighbors_directed(set, Direction::Outgoing) + { + match child { + NodeId::System(_) => { + systems_in_sets.get_mut(&set).unwrap().push(child); + } + NodeId::Set(_) => { + let [sys, child_sys] = + systems_in_sets.get_many_mut([&set, &child]).unwrap(); + sys.extend_from_slice(child_sys); + } + } + } + } + + // can't depend on or be ambiguous with system type sets that have many instances + for (&set, systems) in systems_in_sets.iter() { + let node = &self.system_sets[set.index()]; + if node.is_system_type() { + let ambiguities = self.ambiguous_with.edges(set).count(); + let mut dependencies = 0; + dependencies += self + .dependency + .graph + .edges_directed(set, Direction::Incoming) + .count(); + dependencies += self + .dependency + .graph + .edges_directed(set, Direction::Outgoing) + .count(); + if systems.len() > 1 && (ambiguities > 0 || dependencies > 0) { + return Err(ScheduleBuildError::SystemTypeSetAmbiguity( + node.inner.dyn_clone(), + )); + } + } + } + + // flatten dependency graph + let mut dependency_flattened = DiGraphMap::new(); + for id in self.dependency.graph.nodes() { + if id.is_system() { + dependency_flattened.add_node(id); + } + } + + for (lhs, rhs, _) in self.dependency.graph.all_edges() { + match (lhs, rhs) { + (NodeId::System(_), NodeId::System(_)) => { + dependency_flattened.add_edge(lhs, rhs, ()); + } + (NodeId::Set(_), NodeId::System(_)) => { + for &lhs_ in &systems_in_sets[&lhs] { + dependency_flattened.add_edge(lhs_, rhs, ()); + } + } + (NodeId::System(_), NodeId::Set(_)) => { + for &rhs_ in &systems_in_sets[&rhs] { + dependency_flattened.add_edge(lhs, rhs_, ()); + } + } + (NodeId::Set(_), NodeId::Set(_)) => { + for &lhs_ in &systems_in_sets[&lhs] { + for &rhs_ in &systems_in_sets[&rhs] { + dependency_flattened.add_edge(lhs_, rhs_, ()); + } + } + } + } + } + + // check flattened dependencies for cycles + let flat_scc = tarjan_scc(&dependency_flattened); + if self.contains_cycles(&flat_scc) { + self.report_cycles(&flat_scc); + return Err(ScheduleBuildError::DependencyCycle); + } + + self.dependency_flattened.graph = dependency_flattened; + self.dependency_flattened.topsort = + flat_scc.into_iter().flatten().rev().collect::>(); + + let flat_results = check_graph( + &self.dependency_flattened.graph, + &self.dependency_flattened.topsort, + ); + + // remove redundant edges + self.dependency_flattened.graph = flat_results.transitive_reduction; + + // flatten allowed ambiguities + let mut ambiguous_with_flattened = UnGraphMap::new(); + for (lhs, rhs, _) in self.ambiguous_with.all_edges() { + match (lhs, rhs) { + (NodeId::System(_), NodeId::System(_)) => { + ambiguous_with_flattened.add_edge(lhs, rhs, ()); + } + (NodeId::Set(_), NodeId::System(_)) => { + for &lhs_ in &systems_in_sets[&lhs] { + ambiguous_with_flattened.add_edge(lhs_, rhs, ()); + } + } + (NodeId::System(_), NodeId::Set(_)) => { + for &rhs_ in &systems_in_sets[&rhs] { + ambiguous_with_flattened.add_edge(lhs, rhs_, ()); + } + } + (NodeId::Set(_), NodeId::Set(_)) => { + for &lhs_ in &systems_in_sets[&lhs] { + for &rhs_ in &systems_in_sets[&rhs] { + ambiguous_with_flattened.add_edge(lhs_, rhs_, ()); + } + } + } + } + } + + self.ambiguous_with_flattened = ambiguous_with_flattened; + + // check for conflicts + let mut conflicting_systems = Vec::new(); + for &(a, b) in flat_results.disconnected.iter() { + if self.ambiguous_with_flattened.contains_edge(a, b) + || self.ambiguous_with_all.contains(&a) + || self.ambiguous_with_all.contains(&b) + { + continue; + } + + let system_a = self.systems[a.index()].as_ref().unwrap(); + let system_b = self.systems[b.index()].as_ref().unwrap(); + if system_a.is_exclusive() || system_b.is_exclusive() { + conflicting_systems.push((a, b, Vec::new())); + } else { + let access_a = system_a.component_access(); + let access_b = system_b.component_access(); + if !access_a.is_compatible(access_b) { + let conflicts = access_a.get_conflicts(access_b); + conflicting_systems.push((a, b, conflicts)); + } + } + } + + if self.contains_conflicts(&conflicting_systems) { + self.report_conflicts(&conflicting_systems); + if matches!(self.settings.ambiguity_detection, LogLevel::Error) { + return Err(ScheduleBuildError::Ambiguity); + } + } + + // build the schedule + let dg_system_ids = self.dependency_flattened.topsort.clone(); + let dg_system_idx_map = dg_system_ids + .iter() + .cloned() + .enumerate() + .map(|(i, id)| (id, i)) + .collect::>(); + + let hg_systems = self + .hierarchy + .topsort + .iter() + .cloned() + .enumerate() + .filter(|&(_i, id)| id.is_system()) + .collect::>(); + + let (hg_set_idxs, hg_set_ids): (Vec<_>, Vec<_>) = self + .hierarchy + .topsort + .iter() + .cloned() + .enumerate() + .filter(|&(_i, id)| { + // ignore system sets that have no conditions + // ignore system type sets (already covered, they don't have conditions) + id.is_set() + && self.system_set_conditions[id.index()] + .as_ref() + .filter(|v| !v.is_empty()) + .is_some() + }) + .unzip(); + + let sys_count = self.systems.len(); + let set_count = hg_set_ids.len(); + let node_count = self.systems.len() + self.system_sets.len(); + + // get the number of dependencies and the immediate dependents of each system + // (needed by multi-threaded executor to run systems in the correct order) + let mut system_dependencies = Vec::with_capacity(sys_count); + let mut system_dependents = Vec::with_capacity(sys_count); + for &sys_id in &dg_system_ids { + let num_dependencies = self + .dependency_flattened + .graph + .neighbors_directed(sys_id, Direction::Incoming) + .count(); + + let dependents = self + .dependency_flattened + .graph + .neighbors_directed(sys_id, Direction::Outgoing) + .map(|dep_id| dg_system_idx_map[&dep_id]) + .collect::>(); + + system_dependencies.push(num_dependencies); + system_dependents.push(dependents); + } + + // get the rows and columns of the hierarchy graph's reachability matrix + // (needed to we can evaluate conditions in the correct order) + let mut systems_in_sets = vec![FixedBitSet::with_capacity(sys_count); set_count]; + for (i, &row) in hg_set_idxs.iter().enumerate() { + let bitset = &mut systems_in_sets[i]; + for &(col, sys_id) in &hg_systems { + let idx = dg_system_idx_map[&sys_id]; + let is_descendant = hier_results.reachable[index(row, col, node_count)]; + bitset.set(idx, is_descendant); + } + } + + let mut sets_of_systems = vec![FixedBitSet::with_capacity(set_count); sys_count]; + for &(col, sys_id) in &hg_systems { + let i = dg_system_idx_map[&sys_id]; + let bitset = &mut sets_of_systems[i]; + for (idx, &row) in hg_set_idxs + .iter() + .enumerate() + .take_while(|&(_idx, &row)| row < col) + { + let is_ancestor = hier_results.reachable[index(row, col, node_count)]; + bitset.set(idx, is_ancestor); + } + } + + Ok(SystemSchedule { + systems: Vec::with_capacity(sys_count), + system_conditions: Vec::with_capacity(sys_count), + set_conditions: Vec::with_capacity(set_count), + system_ids: dg_system_ids, + set_ids: hg_set_ids, + system_dependencies, + system_dependents, + sets_of_systems, + systems_in_sets, + }) + } + + fn update_schedule(&mut self, schedule: &mut SystemSchedule) -> Result<(), ScheduleBuildError> { + if !self.uninit.is_empty() { + return Err(ScheduleBuildError::Uninitialized); + } + + // move systems out of old schedule + for ((id, system), conditions) in schedule + .system_ids + .drain(..) + .zip(schedule.systems.drain(..)) + .zip(schedule.system_conditions.drain(..)) + { + self.systems[id.index()] = Some(system); + self.system_conditions[id.index()] = Some(conditions); + } + + for (id, conditions) in schedule + .set_ids + .drain(..) + .zip(schedule.set_conditions.drain(..)) + { + self.system_set_conditions[id.index()] = Some(conditions); + } + + *schedule = self.build_schedule()?; + + // move systems into new schedule + for &id in &schedule.system_ids { + let system = self.systems[id.index()].take().unwrap(); + let conditions = self.system_conditions[id.index()].take().unwrap(); + schedule.systems.push(system); + schedule.system_conditions.push(conditions); + } + + for &id in &schedule.set_ids { + let conditions = self.system_set_conditions[id.index()].take().unwrap(); + schedule.set_conditions.push(conditions); + } + + Ok(()) + } +} + +// methods for reporting errors +impl ScheduleGraph { + fn get_node_name(&self, id: &NodeId) -> String { + match id { + NodeId::System(_) => self.systems[id.index()] + .as_ref() + .unwrap() + .name() + .to_string(), + NodeId::Set(_) => self.system_sets[id.index()].name(), + } + } + + fn get_node_kind(id: &NodeId) -> &'static str { + match id { + NodeId::System(_) => "system", + NodeId::Set(_) => "system set", + } + } + + fn contains_hierarchy_conflicts(&self, transitive_edges: &[(NodeId, NodeId)]) -> bool { + if transitive_edges.is_empty() { + return false; + } + + true + } + + fn report_hierarchy_conflicts(&self, transitive_edges: &[(NodeId, NodeId)]) { + let mut message = String::from("hierarchy contains redundant edge(s)"); + for (parent, child) in transitive_edges { + writeln!( + message, + " -- {:?} '{:?}' cannot be child of set '{:?}', longer path exists", + Self::get_node_kind(child), + self.get_node_name(child), + self.get_node_name(parent), + ) + .unwrap(); + } + + error!("{}", message); + } + + fn contains_cycles(&self, strongly_connected_components: &[Vec]) -> bool { + if strongly_connected_components + .iter() + .all(|scc| scc.len() == 1) + { + return false; + } + + true + } + + fn report_cycles(&self, strongly_connected_components: &[Vec]) { + let components_with_cycles = strongly_connected_components + .iter() + .filter(|scc| scc.len() > 1) + .cloned() + .collect::>(); + + let mut message = format!( + "schedule contains at least {} cycle(s)", + components_with_cycles.len() + ); + + writeln!(message, " -- cycle(s) found within:").unwrap(); + for (i, scc) in components_with_cycles.into_iter().enumerate() { + let names = scc + .iter() + .map(|id| self.get_node_name(id)) + .collect::>(); + writeln!(message, " ---- {i}: {names:?}").unwrap(); + } + + error!("{}", message); + } + + fn contains_conflicts(&self, conflicts: &[(NodeId, NodeId, Vec)]) -> bool { + if conflicts.is_empty() { + return false; + } + + true + } + + fn report_conflicts(&self, ambiguities: &[(NodeId, NodeId, Vec)]) { + let mut string = String::from( + "Some systems with conflicting access have indeterminate execution order. \ + Consider adding `before`, `after`, or `ambiguous_with` relationships between these:\n", + ); + + for (system_a, system_b, conflicts) in ambiguities { + debug_assert!(system_a.is_system()); + debug_assert!(system_b.is_system()); + let name_a = self.get_node_name(system_a); + let name_b = self.get_node_name(system_b); + + writeln!(string, " -- {name_a} and {name_b}").unwrap(); + if !conflicts.is_empty() { + writeln!(string, " conflict on: {conflicts:?}").unwrap(); + } else { + // one or both systems must be exclusive + let world = std::any::type_name::(); + writeln!(string, " conflict on: {world}").unwrap(); + } + } + + warn!("{}", string); + } +} + +/// Category of errors encountered during schedule construction. +#[derive(Error, Debug)] +#[non_exhaustive] +pub enum ScheduleBuildError { + /// A system set contains itself. + #[error("`{0:?}` contains itself.")] + HierarchyLoop(BoxedSystemSet), + /// The hierarchy of system sets contains a cycle. + #[error("System set hierarchy contains cycle(s).")] + HierarchyCycle, + /// The hierarchy of system sets contains redundant edges. + /// + /// This error is disabled by default, but can be opted-in using [`ScheduleBuildSettings`]. + #[error("System set hierarchy contains redundant edges.")] + HierarchyRedundancy, + /// A system (set) has been told to run before itself. + #[error("`{0:?}` depends on itself.")] + DependencyLoop(BoxedSystemSet), + /// The dependency graph contains a cycle. + #[error("System dependencies contain cycle(s).")] + DependencyCycle, + /// Tried to order a system (set) relative to a system set it belongs to. + #[error("`{0:?}` and `{1:?}` have both `in_set` and `before`-`after` relationships (these might be transitive). This combination is unsolvable as a system cannot run before or after a set it belongs to.")] + CrossDependency(String, String), + /// Tried to order a system (set) relative to all instances of some system function. + #[error("Tried to order against `fn {0:?}` in a schedule that has more than one `{0:?}` instance. `fn {0:?}` is a `SystemTypeSet` and cannot be used for ordering if ambiguous. Use a different set without this restriction.")] + SystemTypeSetAmbiguity(BoxedSystemSet), + /// Systems with conflicting access have indeterminate run order. + /// + /// This error is disabled by default, but can be opted-in using [`ScheduleBuildSettings`]. + #[error("Systems with conflicting access have indeterminate run order.")] + Ambiguity, + /// Tried to run a schedule before all of its systems have been initialized. + #[error("Systems in schedule have not been initialized.")] + Uninitialized, +} + +/// Specifies how schedule construction should respond to detecting a certain kind of issue. +pub enum LogLevel { + /// Occurrences are logged only. + Warn, + /// Occurrences are logged and result in errors. + Error, +} + +/// Specifies miscellaneous settings for schedule construction. +pub struct ScheduleBuildSettings { + ambiguity_detection: LogLevel, + hierarchy_detection: LogLevel, +} + +impl Default for ScheduleBuildSettings { + fn default() -> Self { + Self::new() + } +} + +impl ScheduleBuildSettings { + pub const fn new() -> Self { + Self { + ambiguity_detection: LogLevel::Warn, + hierarchy_detection: LogLevel::Warn, + } + } + + /// Determines whether the presence of ambiguities (systems with conflicting access but indeterminate order) + /// is only logged or also results in an [`Ambiguity`](ScheduleBuildError::Ambiguity) error. + pub fn with_ambiguity_detection(mut self, level: LogLevel) -> Self { + self.ambiguity_detection = level; + self + } + + /// Determines whether the presence of redundant edges in the hierarchy of system sets is only + /// logged or also results in a [`HierarchyRedundancy`](ScheduleBuildError::HierarchyRedundancy) + /// error. + pub fn with_hierarchy_detection(mut self, level: LogLevel) -> Self { + self.hierarchy_detection = level; + self + } +} diff --git a/crates/bevy_ecs/src/schedule/state.rs b/crates/bevy_ecs/src/schedule/state.rs new file mode 100644 index 0000000000000..4425f76457c8f --- /dev/null +++ b/crates/bevy_ecs/src/schedule/state.rs @@ -0,0 +1,64 @@ +use std::fmt::Debug; +use std::hash::Hash; +use std::mem; + +use crate as bevy_ecs; +use crate::schedule::{ScheduleLabel, SystemSet, WorldExt}; +use crate::system::Resource; +use crate::world::World; + +/// Types that can define states in a finite-state machine. +pub trait States: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug { + type Iter: Iterator; + + /// Returns an iterator over all the state variants. + fn states() -> Self::Iter; +} + +/// The label of a [`Schedule`](super::Schedule) that runs whenever [`State`] +/// enters this state. +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct OnEnter(pub S); + +/// The label of a [`Schedule`](super::Schedule) that runs whenever [`State`] +/// exits this state. +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct OnExit(pub S); + +/// A [`SystemSet`] that will run within \ when this state is active. +/// +/// This is provided for convenience. A more general [`state_equals`](super::state_equals) +/// [condition](super::Condition) also exists for systems that need to run elsewhere. +#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] +pub struct OnUpdate(pub S); + +/// A finite-state machine whose transitions have associated schedules +/// ([`OnEnter(state)`] and [`OnExit(state)`]). +/// +/// The current state value can be accessed through this resource. To *change* the state, +/// queue a transition in the [`NextState`] resource, and it will be applied by the next +/// [`apply_state_transition::`] system. +#[derive(Resource)] +pub struct State(pub S); + +/// The next state of [`State`]. +/// +/// To queue a transition, just set the contained value to `Some(next_state)`. +#[derive(Resource)] +pub struct NextState(pub Option); + +/// If a new state is queued in [`NextState`], this system: +/// - Takes the new state value from [`NextState`] and updates [`State`]. +/// - Runs the [`OnExit(exited_state)`] schedule. +/// - Runs the [`OnEnter(entered_state)`] schedule. +pub fn apply_state_transition(world: &mut World) { + if world.resource::>().0.is_some() { + let entered_state = world.resource_mut::>().0.take().unwrap(); + let exited_state = mem::replace( + &mut world.resource_mut::>().0, + entered_state.clone(), + ); + world.run_schedule(OnExit(exited_state)); + world.run_schedule(OnEnter(entered_state)); + } +} diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index 1d5c7fca36781..77dda93b30d17 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -159,8 +159,8 @@ where vec![self.func.as_system_label().as_label()] } - fn default_system_sets(&self) -> Vec> { - let set = crate::schedule_v3::SystemTypeSet::::new(); + fn default_system_sets(&self) -> Vec> { + let set = crate::schedule::SystemTypeSet::::new(); vec![Box::new(set)] } } diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 7493f6d3a1584..7f5b1ccf0c928 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -527,8 +527,8 @@ where vec![self.func.as_system_label().as_label()] } - fn default_system_sets(&self) -> Vec> { - let set = crate::schedule_v3::SystemTypeSet::::new(); + fn default_system_sets(&self) -> Vec> { + let set = crate::schedule::SystemTypeSet::::new(); vec![Box::new(set)] } } diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index a6034f0d53829..e046bfc031539 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -69,7 +69,7 @@ pub trait System: Send + Sync + 'static { Vec::new() } /// Returns the system's default [system sets](crate::schedule_v3::SystemSet). - fn default_system_sets(&self) -> Vec> { + fn default_system_sets(&self) -> Vec> { Vec::new() } /// Gets the system's last change tick diff --git a/crates/bevy_ecs/src/system/system_piping.rs b/crates/bevy_ecs/src/system/system_piping.rs index efd545a46a30f..89cd87935cbfa 100644 --- a/crates/bevy_ecs/src/system/system_piping.rs +++ b/crates/bevy_ecs/src/system/system_piping.rs @@ -152,7 +152,7 @@ impl> System for PipeSystem< labels } - fn default_system_sets(&self) -> Vec> { + fn default_system_sets(&self) -> Vec> { let mut system_sets = self.system_a.default_system_sets(); system_sets.extend_from_slice(&self.system_b.default_system_sets()); system_sets diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 066268526e953..2c78661be2500 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1617,7 +1617,7 @@ impl World { resources.check_change_ticks(change_tick); non_send_resources.check_change_ticks(change_tick); - if let Some(mut schedules) = self.get_resource_mut::() { + if let Some(mut schedules) = self.get_resource_mut::() { schedules.check_change_ticks(change_tick); } From 7d13db29b37b73e36aac0cfbb4e5aa8146eebf61 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 10:14:47 -0500 Subject: [PATCH 003/247] Rename schedule v3 to schedule --- crates/bevy_ecs/src/lib.rs | 1 - .../src/{schedule_v3 => schedule}/set.rs | 0 crates/bevy_ecs/src/schedule_v3/condition.rs | 97 -- crates/bevy_ecs/src/schedule_v3/config.rs | 649 ---------- .../bevy_ecs/src/schedule_v3/executor/mod.rs | 90 -- .../schedule_v3/executor/multi_threaded.rs | 575 --------- .../src/schedule_v3/executor/simple.rs | 111 -- .../schedule_v3/executor/single_threaded.rs | 137 -- .../bevy_ecs/src/schedule_v3/graph_utils.rs | 233 ---- crates/bevy_ecs/src/schedule_v3/migration.rs | 38 - crates/bevy_ecs/src/schedule_v3/mod.rs | 622 ---------- crates/bevy_ecs/src/schedule_v3/schedule.rs | 1099 ----------------- crates/bevy_ecs/src/schedule_v3/state.rs | 64 - 13 files changed, 3716 deletions(-) rename crates/bevy_ecs/src/{schedule_v3 => schedule}/set.rs (100%) delete mode 100644 crates/bevy_ecs/src/schedule_v3/condition.rs delete mode 100644 crates/bevy_ecs/src/schedule_v3/config.rs delete mode 100644 crates/bevy_ecs/src/schedule_v3/executor/mod.rs delete mode 100644 crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs delete mode 100644 crates/bevy_ecs/src/schedule_v3/executor/simple.rs delete mode 100644 crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs delete mode 100644 crates/bevy_ecs/src/schedule_v3/graph_utils.rs delete mode 100644 crates/bevy_ecs/src/schedule_v3/migration.rs delete mode 100644 crates/bevy_ecs/src/schedule_v3/mod.rs delete mode 100644 crates/bevy_ecs/src/schedule_v3/schedule.rs delete mode 100644 crates/bevy_ecs/src/schedule_v3/state.rs diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index b0a9f9f155a06..47d93beab837d 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -14,7 +14,6 @@ pub mod query; #[cfg(feature = "bevy_reflect")] pub mod reflect; pub mod schedule; -pub mod schedule_v3; pub mod storage; pub mod system; pub mod world; diff --git a/crates/bevy_ecs/src/schedule_v3/set.rs b/crates/bevy_ecs/src/schedule/set.rs similarity index 100% rename from crates/bevy_ecs/src/schedule_v3/set.rs rename to crates/bevy_ecs/src/schedule/set.rs diff --git a/crates/bevy_ecs/src/schedule_v3/condition.rs b/crates/bevy_ecs/src/schedule_v3/condition.rs deleted file mode 100644 index e617375f763a0..0000000000000 --- a/crates/bevy_ecs/src/schedule_v3/condition.rs +++ /dev/null @@ -1,97 +0,0 @@ -pub use common_conditions::*; - -use crate::system::BoxedSystem; - -pub type BoxedCondition = BoxedSystem<(), bool>; - -/// A system that determines if one or more scheduled systems should run. -/// -/// Implemented for functions and closures that convert into [`System`](crate::system::System) -/// with [read-only](crate::system::ReadOnlySystemParam) parameters. -pub trait Condition: sealed::Condition {} - -impl Condition for F where F: sealed::Condition {} - -mod sealed { - use crate::system::{IntoSystem, IsFunctionSystem, ReadOnlySystemParam, SystemParamFunction}; - - pub trait Condition: IntoSystem<(), bool, Params> {} - - impl Condition<(IsFunctionSystem, Params, Marker)> for F - where - F: SystemParamFunction<(), bool, Params, Marker> + Send + Sync + 'static, - Params: ReadOnlySystemParam + 'static, - Marker: 'static, - { - } -} - -mod common_conditions { - use crate::schedule_v3::{State, States}; - use crate::system::{Res, Resource}; - - /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` - /// if the resource exists. - pub fn resource_exists() -> impl FnMut(Option>) -> bool - where - T: Resource, - { - move |res: Option>| res.is_some() - } - - /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` - /// if the resource is equal to `value`. - /// - /// # Panics - /// - /// The condition will panic if the resource does not exist. - pub fn resource_equals(value: T) -> impl FnMut(Res) -> bool - where - T: Resource + PartialEq, - { - move |res: Res| *res == value - } - - /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` - /// if the resource exists and is equal to `value`. - /// - /// The condition will return `false` if the resource does not exist. - pub fn resource_exists_and_equals(value: T) -> impl FnMut(Option>) -> bool - where - T: Resource + PartialEq, - { - move |res: Option>| match res { - Some(res) => *res == value, - None => false, - } - } - - /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` - /// if the state machine exists. - pub fn state_exists() -> impl FnMut(Option>>) -> bool { - move |current_state: Option>>| current_state.is_some() - } - - /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` - /// if the state machine is currently in `state`. - /// - /// # Panics - /// - /// The condition will panic if the resource does not exist. - pub fn state_equals(state: S) -> impl FnMut(Res>) -> bool { - move |current_state: Res>| current_state.0 == state - } - - /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` - /// if the state machine exists and is currently in `state`. - /// - /// The condition will return `false` if the state does not exist. - pub fn state_exists_and_equals( - state: S, - ) -> impl FnMut(Option>>) -> bool { - move |current_state: Option>>| match current_state { - Some(current_state) => current_state.0 == state, - None => false, - } - } -} diff --git a/crates/bevy_ecs/src/schedule_v3/config.rs b/crates/bevy_ecs/src/schedule_v3/config.rs deleted file mode 100644 index d7a87c374c047..0000000000000 --- a/crates/bevy_ecs/src/schedule_v3/config.rs +++ /dev/null @@ -1,649 +0,0 @@ -use bevy_ecs_macros::all_tuples; -use bevy_utils::default; - -use crate::{ - schedule_v3::{ - condition::{BoxedCondition, Condition}, - graph_utils::{Ambiguity, Dependency, DependencyKind, GraphInfo}, - set::{BoxedSystemSet, IntoSystemSet, SystemSet}, - }, - system::{BoxedSystem, IntoSystem, System}, -}; - -/// A [`SystemSet`] with scheduling metadata. -pub struct SystemSetConfig { - pub(super) set: BoxedSystemSet, - pub(super) graph_info: GraphInfo, - pub(super) conditions: Vec, -} - -impl SystemSetConfig { - fn new(set: BoxedSystemSet) -> Self { - // system type sets are automatically populated - // to avoid unintentionally broad changes, they cannot be configured - assert!( - !set.is_system_type(), - "configuring system type sets is not allowed" - ); - - Self { - set, - graph_info: GraphInfo { - sets: Vec::new(), - dependencies: Vec::new(), - ambiguous_with: default(), - }, - conditions: Vec::new(), - } - } -} - -/// A [`System`] with scheduling metadata. -pub struct SystemConfig { - pub(super) system: BoxedSystem, - pub(super) graph_info: GraphInfo, - pub(super) conditions: Vec, -} - -impl SystemConfig { - fn new(system: BoxedSystem) -> Self { - // include system in its default sets - let sets = system.default_system_sets().into_iter().collect(); - Self { - system, - graph_info: GraphInfo { - sets, - dependencies: Vec::new(), - ambiguous_with: default(), - }, - conditions: Vec::new(), - } - } -} - -fn new_condition

(condition: impl Condition

) -> BoxedCondition { - let condition_system = IntoSystem::into_system(condition); - assert!( - condition_system.is_send(), - "Condition `{}` accesses thread-local resources. This is not currently supported.", - condition_system.name() - ); - - Box::new(condition_system) -} - -fn ambiguous_with(graph_info: &mut GraphInfo, set: BoxedSystemSet) { - match &mut graph_info.ambiguous_with { - detection @ Ambiguity::Check => { - *detection = Ambiguity::IgnoreWithSet(vec![set]); - } - Ambiguity::IgnoreWithSet(ambiguous_with) => { - ambiguous_with.push(set); - } - Ambiguity::IgnoreAll => (), - } -} - -/// Types that can be converted into a [`SystemSetConfig`]. -/// -/// This has been implemented for all types that implement [`SystemSet`] and boxed trait objects. -pub trait IntoSystemSetConfig: sealed::IntoSystemSetConfig { - /// Convert into a [`SystemSetConfig`]. - #[doc(hidden)] - fn into_config(self) -> SystemSetConfig; - /// Add to the provided `set`. - fn in_set(self, set: impl SystemSet) -> SystemSetConfig; - /// Run before all systems in `set`. - fn before(self, set: impl IntoSystemSet) -> SystemSetConfig; - /// Run after all systems in `set`. - fn after(self, set: impl IntoSystemSet) -> SystemSetConfig; - /// Run the systems in this set only if the [`Condition`] is `true`. - /// - /// The `Condition` will be evaluated at most once (per schedule run), - /// the first time a system in this set prepares to run. - fn run_if

(self, condition: impl Condition

) -> SystemSetConfig; - /// Suppress warnings and errors that would result from systems in this set having ambiguities - /// (conflicting access but indeterminate order) with systems in `set`. - fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemSetConfig; - /// Suppress warnings and errors that would result from systems in this set having ambiguities - /// (conflicting access but indeterminate order) with any other system. - fn ambiguous_with_all(self) -> SystemSetConfig; -} - -impl IntoSystemSetConfig for S -where - S: SystemSet + sealed::IntoSystemSetConfig, -{ - fn into_config(self) -> SystemSetConfig { - SystemSetConfig::new(Box::new(self)) - } - - fn in_set(self, set: impl SystemSet) -> SystemSetConfig { - SystemSetConfig::new(Box::new(self)).in_set(set) - } - - fn before(self, set: impl IntoSystemSet) -> SystemSetConfig { - SystemSetConfig::new(Box::new(self)).before(set) - } - - fn after(self, set: impl IntoSystemSet) -> SystemSetConfig { - SystemSetConfig::new(Box::new(self)).after(set) - } - - fn run_if

(self, condition: impl Condition

) -> SystemSetConfig { - SystemSetConfig::new(Box::new(self)).run_if(condition) - } - - fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemSetConfig { - SystemSetConfig::new(Box::new(self)).ambiguous_with(set) - } - - fn ambiguous_with_all(self) -> SystemSetConfig { - SystemSetConfig::new(Box::new(self)).ambiguous_with_all() - } -} - -impl IntoSystemSetConfig for BoxedSystemSet { - fn into_config(self) -> SystemSetConfig { - SystemSetConfig::new(self) - } - - fn in_set(self, set: impl SystemSet) -> SystemSetConfig { - SystemSetConfig::new(self).in_set(set) - } - - fn before(self, set: impl IntoSystemSet) -> SystemSetConfig { - SystemSetConfig::new(self).before(set) - } - - fn after(self, set: impl IntoSystemSet) -> SystemSetConfig { - SystemSetConfig::new(self).after(set) - } - - fn run_if

(self, condition: impl Condition

) -> SystemSetConfig { - SystemSetConfig::new(self).run_if(condition) - } - - fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemSetConfig { - SystemSetConfig::new(self).ambiguous_with(set) - } - - fn ambiguous_with_all(self) -> SystemSetConfig { - SystemSetConfig::new(self).ambiguous_with_all() - } -} - -impl IntoSystemSetConfig for SystemSetConfig { - fn into_config(self) -> Self { - self - } - - fn in_set(mut self, set: impl SystemSet) -> Self { - assert!( - !set.is_system_type(), - "adding arbitrary systems to a system type set is not allowed" - ); - self.graph_info.sets.push(Box::new(set)); - self - } - - fn before(mut self, set: impl IntoSystemSet) -> Self { - self.graph_info.dependencies.push(Dependency::new( - DependencyKind::Before, - Box::new(set.into_system_set()), - )); - self - } - - fn after(mut self, set: impl IntoSystemSet) -> Self { - self.graph_info.dependencies.push(Dependency::new( - DependencyKind::After, - Box::new(set.into_system_set()), - )); - self - } - - fn run_if

(mut self, condition: impl Condition

) -> Self { - self.conditions.push(new_condition(condition)); - self - } - - fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { - ambiguous_with(&mut self.graph_info, Box::new(set.into_system_set())); - self - } - - fn ambiguous_with_all(mut self) -> Self { - self.graph_info.ambiguous_with = Ambiguity::IgnoreAll; - self - } -} - -/// Types that can be converted into a [`SystemConfig`]. -/// -/// This has been implemented for boxed [`System`](crate::system::System) -/// trait objects and all functions that turn into such. -pub trait IntoSystemConfig: sealed::IntoSystemConfig { - /// Convert into a [`SystemConfig`]. - #[doc(hidden)] - fn into_config(self) -> SystemConfig; - /// Add to `set` membership. - fn in_set(self, set: impl SystemSet) -> SystemConfig; - /// Run before all systems in `set`. - fn before(self, set: impl IntoSystemSet) -> SystemConfig; - /// Run after all systems in `set`. - fn after(self, set: impl IntoSystemSet) -> SystemConfig; - /// Run only if the [`Condition`] is `true`. - /// - /// The `Condition` will be evaluated at most once (per schedule run), - /// when the system prepares to run. - fn run_if

(self, condition: impl Condition

) -> SystemConfig; - /// Suppress warnings and errors that would result from this system having ambiguities - /// (conflicting access but indeterminate order) with systems in `set`. - fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemConfig; - /// Suppress warnings and errors that would result from this system having ambiguities - /// (conflicting access but indeterminate order) with any other system. - fn ambiguous_with_all(self) -> SystemConfig; -} - -impl IntoSystemConfig for F -where - F: IntoSystem<(), (), Params> + sealed::IntoSystemConfig, -{ - fn into_config(self) -> SystemConfig { - SystemConfig::new(Box::new(IntoSystem::into_system(self))) - } - - fn in_set(self, set: impl SystemSet) -> SystemConfig { - SystemConfig::new(Box::new(IntoSystem::into_system(self))).in_set(set) - } - - fn before(self, set: impl IntoSystemSet) -> SystemConfig { - SystemConfig::new(Box::new(IntoSystem::into_system(self))).before(set) - } - - fn after(self, set: impl IntoSystemSet) -> SystemConfig { - SystemConfig::new(Box::new(IntoSystem::into_system(self))).after(set) - } - - fn run_if

(self, condition: impl Condition

) -> SystemConfig { - SystemConfig::new(Box::new(IntoSystem::into_system(self))).run_if(condition) - } - - fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemConfig { - SystemConfig::new(Box::new(IntoSystem::into_system(self))).ambiguous_with(set) - } - - fn ambiguous_with_all(self) -> SystemConfig { - SystemConfig::new(Box::new(IntoSystem::into_system(self))).ambiguous_with_all() - } -} - -impl IntoSystemConfig<()> for BoxedSystem<(), ()> { - fn into_config(self) -> SystemConfig { - SystemConfig::new(self) - } - - fn in_set(self, set: impl SystemSet) -> SystemConfig { - SystemConfig::new(self).in_set(set) - } - - fn before(self, set: impl IntoSystemSet) -> SystemConfig { - SystemConfig::new(self).before(set) - } - - fn after(self, set: impl IntoSystemSet) -> SystemConfig { - SystemConfig::new(self).after(set) - } - - fn run_if

(self, condition: impl Condition

) -> SystemConfig { - SystemConfig::new(self).run_if(condition) - } - - fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemConfig { - SystemConfig::new(self).ambiguous_with(set) - } - - fn ambiguous_with_all(self) -> SystemConfig { - SystemConfig::new(self).ambiguous_with_all() - } -} - -impl IntoSystemConfig<()> for SystemConfig { - fn into_config(self) -> Self { - self - } - - fn in_set(mut self, set: impl SystemSet) -> Self { - assert!( - !set.is_system_type(), - "adding arbitrary systems to a system type set is not allowed" - ); - self.graph_info.sets.push(Box::new(set)); - self - } - - fn before(mut self, set: impl IntoSystemSet) -> Self { - self.graph_info.dependencies.push(Dependency::new( - DependencyKind::Before, - Box::new(set.into_system_set()), - )); - self - } - - fn after(mut self, set: impl IntoSystemSet) -> Self { - self.graph_info.dependencies.push(Dependency::new( - DependencyKind::After, - Box::new(set.into_system_set()), - )); - self - } - - fn run_if

(mut self, condition: impl Condition

) -> Self { - self.conditions.push(new_condition(condition)); - self - } - - fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { - ambiguous_with(&mut self.graph_info, Box::new(set.into_system_set())); - self - } - - fn ambiguous_with_all(mut self) -> Self { - self.graph_info.ambiguous_with = Ambiguity::IgnoreAll; - self - } -} - -// only `System` system objects can be scheduled -mod sealed { - use crate::{ - schedule_v3::{BoxedSystemSet, SystemSet}, - system::{BoxedSystem, IntoSystem}, - }; - - use super::{SystemConfig, SystemSetConfig}; - - pub trait IntoSystemConfig {} - - impl> IntoSystemConfig for F {} - - impl IntoSystemConfig<()> for BoxedSystem<(), ()> {} - - impl IntoSystemConfig<()> for SystemConfig {} - - pub trait IntoSystemSetConfig {} - - impl IntoSystemSetConfig for S {} - - impl IntoSystemSetConfig for BoxedSystemSet {} - - impl IntoSystemSetConfig for SystemSetConfig {} -} - -/// A collection of [`SystemConfig`]. -pub struct SystemConfigs { - pub(super) systems: Vec, - /// If `true`, adds `before -> after` ordering constraints between the successive elements. - pub(super) chained: bool, -} - -/// Types that can convert into a [`SystemConfigs`]. -pub trait IntoSystemConfigs -where - Self: Sized, -{ - /// Convert into a [`SystemConfigs`]. - #[doc(hidden)] - fn into_configs(self) -> SystemConfigs; - - /// Add these systems to the provided `set`. - fn in_set(self, set: impl SystemSet) -> SystemConfigs { - self.into_configs().in_set(set) - } - - /// Run before all systems in `set`. - fn before(self, set: impl IntoSystemSet) -> SystemConfigs { - self.into_configs().before(set) - } - - /// Run after all systems in `set`. - fn after(self, set: impl IntoSystemSet) -> SystemConfigs { - self.into_configs().after(set) - } - - /// Suppress warnings and errors that would result from these systems having ambiguities - /// (conflicting access but indeterminate order) with systems in `set`. - fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemConfigs { - self.into_configs().ambiguous_with(set) - } - - /// Suppress warnings and errors that would result from these systems having ambiguities - /// (conflicting access but indeterminate order) with any other system. - fn ambiguous_with_all(self) -> SystemConfigs { - self.into_configs().ambiguous_with_all() - } - - /// Treat this collection as a sequence of systems. - /// - /// Ordering constraints will be applied between the successive elements. - fn chain(self) -> SystemConfigs { - self.into_configs().chain() - } -} - -impl IntoSystemConfigs<()> for SystemConfigs { - fn into_configs(self) -> Self { - self - } - - fn in_set(mut self, set: impl SystemSet) -> Self { - assert!( - !set.is_system_type(), - "adding arbitrary systems to a system type set is not allowed" - ); - for config in &mut self.systems { - config.graph_info.sets.push(set.dyn_clone()); - } - - self - } - - fn before(mut self, set: impl IntoSystemSet) -> Self { - let set = set.into_system_set(); - for config in &mut self.systems { - config - .graph_info - .dependencies - .push(Dependency::new(DependencyKind::Before, set.dyn_clone())); - } - - self - } - - fn after(mut self, set: impl IntoSystemSet) -> Self { - let set = set.into_system_set(); - for config in &mut self.systems { - config - .graph_info - .dependencies - .push(Dependency::new(DependencyKind::After, set.dyn_clone())); - } - - self - } - - fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { - let set = set.into_system_set(); - for config in &mut self.systems { - ambiguous_with(&mut config.graph_info, set.dyn_clone()); - } - - self - } - - fn ambiguous_with_all(mut self) -> Self { - for config in &mut self.systems { - config.graph_info.ambiguous_with = Ambiguity::IgnoreAll; - } - - self - } - - fn chain(mut self) -> Self { - self.chained = true; - self - } -} - -/// A collection of [`SystemSetConfig`]. -pub struct SystemSetConfigs { - pub(super) sets: Vec, - /// If `true`, adds `before -> after` ordering constraints between the successive elements. - pub(super) chained: bool, -} - -/// Types that can convert into a [`SystemSetConfigs`]. -pub trait IntoSystemSetConfigs -where - Self: Sized, -{ - /// Convert into a [`SystemSetConfigs`]. - #[doc(hidden)] - fn into_configs(self) -> SystemSetConfigs; - - /// Add these system sets to the provided `set`. - fn in_set(self, set: impl SystemSet) -> SystemSetConfigs { - self.into_configs().in_set(set) - } - - /// Run before all systems in `set`. - fn before(self, set: impl IntoSystemSet) -> SystemSetConfigs { - self.into_configs().before(set) - } - - /// Run after all systems in `set`. - fn after(self, set: impl IntoSystemSet) -> SystemSetConfigs { - self.into_configs().after(set) - } - - /// Suppress warnings and errors that would result from systems in these sets having ambiguities - /// (conflicting access but indeterminate order) with systems in `set`. - fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemSetConfigs { - self.into_configs().ambiguous_with(set) - } - - /// Suppress warnings and errors that would result from systems in these sets having ambiguities - /// (conflicting access but indeterminate order) with any other system. - fn ambiguous_with_all(self) -> SystemSetConfigs { - self.into_configs().ambiguous_with_all() - } - - /// Treat this collection as a sequence of system sets. - /// - /// Ordering constraints will be applied between the successive elements. - fn chain(self) -> SystemSetConfigs { - self.into_configs().chain() - } -} - -impl IntoSystemSetConfigs for SystemSetConfigs { - fn into_configs(self) -> Self { - self - } - - fn in_set(mut self, set: impl SystemSet) -> Self { - assert!( - !set.is_system_type(), - "adding arbitrary systems to a system type set is not allowed" - ); - for config in &mut self.sets { - config.graph_info.sets.push(set.dyn_clone()); - } - - self - } - - fn before(mut self, set: impl IntoSystemSet) -> Self { - let set = set.into_system_set(); - for config in &mut self.sets { - config - .graph_info - .dependencies - .push(Dependency::new(DependencyKind::Before, set.dyn_clone())); - } - - self - } - - fn after(mut self, set: impl IntoSystemSet) -> Self { - let set = set.into_system_set(); - for config in &mut self.sets { - config - .graph_info - .dependencies - .push(Dependency::new(DependencyKind::After, set.dyn_clone())); - } - - self - } - - fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { - let set = set.into_system_set(); - for config in &mut self.sets { - ambiguous_with(&mut config.graph_info, set.dyn_clone()); - } - - self - } - - fn ambiguous_with_all(mut self) -> Self { - for config in &mut self.sets { - config.graph_info.ambiguous_with = Ambiguity::IgnoreAll; - } - - self - } - - fn chain(mut self) -> Self { - self.chained = true; - self - } -} - -macro_rules! impl_system_collection { - ($(($param: ident, $sys: ident)),*) => { - impl<$($param, $sys),*> IntoSystemConfigs<($($param,)*)> for ($($sys,)*) - where - $($sys: IntoSystemConfig<$param>),* - { - #[allow(non_snake_case)] - fn into_configs(self) -> SystemConfigs { - let ($($sys,)*) = self; - SystemConfigs { - systems: vec![$($sys.into_config(),)*], - chained: false, - } - } - } - } -} - -macro_rules! impl_system_set_collection { - ($($set: ident),*) => { - impl<$($set: IntoSystemSetConfig),*> IntoSystemSetConfigs for ($($set,)*) - { - #[allow(non_snake_case)] - fn into_configs(self) -> SystemSetConfigs { - let ($($set,)*) = self; - SystemSetConfigs { - sets: vec![$($set.into_config(),)*], - chained: false, - } - } - } - } -} - -all_tuples!(impl_system_collection, 0, 15, P, S); -all_tuples!(impl_system_set_collection, 0, 15, S); diff --git a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs deleted file mode 100644 index bfc1eef14d609..0000000000000 --- a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs +++ /dev/null @@ -1,90 +0,0 @@ -mod multi_threaded; -mod simple; -mod single_threaded; - -pub use self::multi_threaded::MultiThreadedExecutor; -pub use self::simple::SimpleExecutor; -pub use self::single_threaded::SingleThreadedExecutor; - -use fixedbitset::FixedBitSet; - -use crate::{ - schedule_v3::{BoxedCondition, NodeId}, - system::BoxedSystem, - world::World, -}; - -/// Types that can run a [`SystemSchedule`] on a [`World`]. -pub(super) trait SystemExecutor: Send + Sync { - fn kind(&self) -> ExecutorKind; - fn init(&mut self, schedule: &SystemSchedule); - fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World); -} - -/// Specifies how a [`Schedule`](super::Schedule) will be run. -/// -/// [`MultiThreaded`](ExecutorKind::MultiThreaded) is the default. -#[derive(PartialEq, Eq, Default)] -pub enum ExecutorKind { - /// Runs the schedule using a single thread. - /// - /// Useful if you're dealing with a single-threaded environment, saving your threads for - /// other things, or just trying minimize overhead. - SingleThreaded, - /// Like [`SingleThreaded`](ExecutorKind::SingleThreaded) but calls [`apply_buffers`](crate::system::System::apply_buffers) - /// immediately after running each system. - Simple, - /// Runs the schedule using a thread pool. Non-conflicting systems can run in parallel. - #[default] - MultiThreaded, -} - -/// Holds systems and conditions of a [`Schedule`](super::Schedule) sorted in topological order -/// (along with dependency information for multi-threaded execution). -/// -/// Since the arrays are sorted in the same order, elements are referenced by their index. -/// `FixedBitSet` is used as a smaller, more efficient substitute of `HashSet`. -#[derive(Default)] -pub(super) struct SystemSchedule { - pub(super) systems: Vec, - pub(super) system_conditions: Vec>, - pub(super) set_conditions: Vec>, - pub(super) system_ids: Vec, - pub(super) set_ids: Vec, - pub(super) system_dependencies: Vec, - pub(super) system_dependents: Vec>, - pub(super) sets_of_systems: Vec, - pub(super) systems_in_sets: Vec, -} - -impl SystemSchedule { - pub const fn new() -> Self { - Self { - systems: Vec::new(), - system_conditions: Vec::new(), - set_conditions: Vec::new(), - system_ids: Vec::new(), - set_ids: Vec::new(), - system_dependencies: Vec::new(), - system_dependents: Vec::new(), - sets_of_systems: Vec::new(), - systems_in_sets: Vec::new(), - } - } -} - -/// Instructs the executor to call [`apply_buffers`](crate::system::System::apply_buffers) -/// on the systems that have run but not applied their buffers. -/// -/// **Notes** -/// - This function (currently) does nothing if it's called manually or wrapped inside a [`PipeSystem`](crate::system::PipeSystem). -/// - Modifying a [`Schedule`](super::Schedule) may change the order buffers are applied. -#[allow(unused_variables)] -pub fn apply_system_buffers(world: &mut World) {} - -/// Returns `true` if the [`System`](crate::system::System) is an instance of [`apply_system_buffers`]. -pub(super) fn is_apply_system_buffers(system: &BoxedSystem) -> bool { - use std::any::Any; - // deref to use `System::type_id` instead of `Any::type_id` - system.as_ref().type_id() == apply_system_buffers.type_id() -} diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs deleted file mode 100644 index 3debba3ac59b4..0000000000000 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ /dev/null @@ -1,575 +0,0 @@ -use bevy_tasks::{ComputeTaskPool, Scope, TaskPool}; -use bevy_utils::default; -use bevy_utils::syncunsafecell::SyncUnsafeCell; -#[cfg(feature = "trace")] -use bevy_utils::tracing::{info_span, Instrument}; - -use async_channel::{Receiver, Sender}; -use fixedbitset::FixedBitSet; - -use crate::{ - archetype::ArchetypeComponentId, - query::Access, - schedule_v3::{ - is_apply_system_buffers, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule, - }, - system::BoxedSystem, - world::World, -}; - -/// A funky borrow split of [`SystemSchedule`] required by the [`MultiThreadedExecutor`]. -struct SyncUnsafeSchedule<'a> { - systems: &'a [SyncUnsafeCell], - conditions: Conditions<'a>, -} - -struct Conditions<'a> { - system_conditions: &'a mut [Vec], - set_conditions: &'a mut [Vec], - sets_of_systems: &'a [FixedBitSet], - systems_in_sets: &'a [FixedBitSet], -} - -impl SyncUnsafeSchedule<'_> { - fn new(schedule: &mut SystemSchedule) -> SyncUnsafeSchedule<'_> { - SyncUnsafeSchedule { - systems: SyncUnsafeCell::from_mut(schedule.systems.as_mut_slice()).as_slice_of_cells(), - conditions: Conditions { - system_conditions: &mut schedule.system_conditions, - set_conditions: &mut schedule.set_conditions, - sets_of_systems: &schedule.sets_of_systems, - systems_in_sets: &schedule.systems_in_sets, - }, - } - } -} - -/// Per-system data used by the [`MultiThreadedExecutor`]. -// Copied here because it can't be read from the system when it's running. -struct SystemTaskMetadata { - /// The `ArchetypeComponentId` access of the system. - archetype_component_access: Access, - /// Indices of the systems that directly depend on the system. - dependents: Vec, - /// Is `true` if the system does not access `!Send` data. - is_send: bool, - /// Is `true` if the system is exclusive. - is_exclusive: bool, -} - -/// Runs the schedule using a thread pool. Non-conflicting systems can run in parallel. -pub struct MultiThreadedExecutor { - /// Sends system completion events. - sender: Sender, - /// Receives system completion events. - receiver: Receiver, - /// Metadata for scheduling and running system tasks. - system_task_metadata: Vec, - /// Union of the accesses of all currently running systems. - active_access: Access, - /// Returns `true` if a system with non-`Send` access is running. - local_thread_running: bool, - /// Returns `true` if an exclusive system is running. - exclusive_running: bool, - /// The number of systems that are running. - num_running_systems: usize, - /// The number of systems that have completed. - num_completed_systems: usize, - /// The number of dependencies each system has that have not completed. - num_dependencies_remaining: Vec, - /// System sets whose conditions have been evaluated. - evaluated_sets: FixedBitSet, - /// Systems that have no remaining dependencies and are waiting to run. - ready_systems: FixedBitSet, - /// copy of `ready_systems` - ready_systems_copy: FixedBitSet, - /// Systems that are running. - running_systems: FixedBitSet, - /// Systems that got skipped. - skipped_systems: FixedBitSet, - /// Systems whose conditions have been evaluated and were run or skipped. - completed_systems: FixedBitSet, - /// Systems that have run but have not had their buffers applied. - unapplied_systems: FixedBitSet, -} - -impl Default for MultiThreadedExecutor { - fn default() -> Self { - Self::new() - } -} - -impl SystemExecutor for MultiThreadedExecutor { - fn kind(&self) -> ExecutorKind { - ExecutorKind::MultiThreaded - } - - fn init(&mut self, schedule: &SystemSchedule) { - // pre-allocate space - let sys_count = schedule.system_ids.len(); - let set_count = schedule.set_ids.len(); - - self.evaluated_sets = FixedBitSet::with_capacity(set_count); - self.ready_systems = FixedBitSet::with_capacity(sys_count); - self.ready_systems_copy = FixedBitSet::with_capacity(sys_count); - self.running_systems = FixedBitSet::with_capacity(sys_count); - self.completed_systems = FixedBitSet::with_capacity(sys_count); - self.skipped_systems = FixedBitSet::with_capacity(sys_count); - self.unapplied_systems = FixedBitSet::with_capacity(sys_count); - - self.system_task_metadata = Vec::with_capacity(sys_count); - for index in 0..sys_count { - self.system_task_metadata.push(SystemTaskMetadata { - archetype_component_access: default(), - dependents: schedule.system_dependents[index].clone(), - is_send: schedule.systems[index].is_send(), - is_exclusive: schedule.systems[index].is_exclusive(), - }); - } - - self.num_dependencies_remaining = Vec::with_capacity(sys_count); - } - - fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) { - // reset counts - let num_systems = schedule.systems.len(); - self.num_running_systems = 0; - self.num_completed_systems = 0; - self.num_dependencies_remaining.clear(); - self.num_dependencies_remaining - .extend_from_slice(&schedule.system_dependencies); - - for (system_index, dependencies) in self.num_dependencies_remaining.iter_mut().enumerate() { - if *dependencies == 0 { - self.ready_systems.insert(system_index); - } - } - - let world = SyncUnsafeCell::from_mut(world); - let SyncUnsafeSchedule { - systems, - mut conditions, - } = SyncUnsafeSchedule::new(schedule); - - ComputeTaskPool::init(TaskPool::default).scope(|scope| { - // the executor itself is a `Send` future so that it can run - // alongside systems that claim the local thread - let executor = async { - while self.num_completed_systems < num_systems { - // SAFETY: self.ready_systems does not contain running systems - unsafe { - self.spawn_system_tasks(scope, systems, &mut conditions, world); - } - - if self.num_running_systems > 0 { - // wait for systems to complete - let index = self - .receiver - .recv() - .await - .unwrap_or_else(|error| unreachable!("{}", error)); - - self.finish_system_and_signal_dependents(index); - - while let Ok(index) = self.receiver.try_recv() { - self.finish_system_and_signal_dependents(index); - } - - self.rebuild_active_access(); - } - } - - // SAFETY: all systems have completed - let world = unsafe { &mut *world.get() }; - apply_system_buffers(&mut self.unapplied_systems, systems, world); - - debug_assert!(self.ready_systems.is_clear()); - debug_assert!(self.running_systems.is_clear()); - debug_assert!(self.unapplied_systems.is_clear()); - self.active_access.clear(); - self.evaluated_sets.clear(); - self.skipped_systems.clear(); - self.completed_systems.clear(); - }; - - #[cfg(feature = "trace")] - let executor_span = info_span!("schedule_task"); - #[cfg(feature = "trace")] - let executor = executor.instrument(executor_span); - scope.spawn(executor); - }); - } -} - -impl MultiThreadedExecutor { - pub fn new() -> Self { - let (sender, receiver) = async_channel::unbounded(); - Self { - sender, - receiver, - system_task_metadata: Vec::new(), - num_running_systems: 0, - num_completed_systems: 0, - num_dependencies_remaining: Vec::new(), - active_access: default(), - local_thread_running: false, - exclusive_running: false, - evaluated_sets: FixedBitSet::new(), - ready_systems: FixedBitSet::new(), - ready_systems_copy: FixedBitSet::new(), - running_systems: FixedBitSet::new(), - skipped_systems: FixedBitSet::new(), - completed_systems: FixedBitSet::new(), - unapplied_systems: FixedBitSet::new(), - } - } - - /// # Safety - /// Caller must ensure that `self.ready_systems` does not contain any systems that - /// have been mutably borrowed (such as the systems currently running). - unsafe fn spawn_system_tasks<'scope>( - &mut self, - scope: &Scope<'_, 'scope, ()>, - systems: &'scope [SyncUnsafeCell], - conditions: &mut Conditions, - cell: &'scope SyncUnsafeCell, - ) { - if self.exclusive_running { - return; - } - - // can't borrow since loop mutably borrows `self` - let mut ready_systems = std::mem::take(&mut self.ready_systems_copy); - ready_systems.clear(); - ready_systems.union_with(&self.ready_systems); - - for system_index in ready_systems.ones() { - assert!(!self.running_systems.contains(system_index)); - // SAFETY: Caller assured that these systems are not running. - // Therefore, no other reference to this system exists and there is no aliasing. - let system = unsafe { &mut *systems[system_index].get() }; - - // SAFETY: No exclusive system is running. - // Therefore, there is no existing mutable reference to the world. - let world = unsafe { &*cell.get() }; - if !self.can_run(system_index, system, conditions, world) { - // NOTE: exclusive systems with ambiguities are susceptible to - // being significantly displaced here (compared to single-threaded order) - // if systems after them in topological order can run - // if that becomes an issue, `break;` if exclusive system - continue; - } - - self.ready_systems.set(system_index, false); - - if !self.should_run(system_index, system, conditions, world) { - self.skip_system_and_signal_dependents(system_index); - continue; - } - - self.running_systems.insert(system_index); - self.num_running_systems += 1; - - if self.system_task_metadata[system_index].is_exclusive { - // SAFETY: `can_run` confirmed that no systems are running. - // Therefore, there is no existing reference to the world. - unsafe { - let world = &mut *cell.get(); - self.spawn_exclusive_system_task(scope, system_index, systems, world); - } - break; - } - - // SAFETY: No other reference to this system exists. - unsafe { - self.spawn_system_task(scope, system_index, systems, world); - } - } - - // give back - self.ready_systems_copy = ready_systems; - } - - fn can_run( - &mut self, - system_index: usize, - system: &mut BoxedSystem, - conditions: &mut Conditions, - world: &World, - ) -> bool { - #[cfg(feature = "trace")] - let _span = info_span!("check_access", name = &*system.name()).entered(); - - let system_meta = &self.system_task_metadata[system_index]; - if system_meta.is_exclusive && self.num_running_systems > 0 { - return false; - } - - if !system_meta.is_send && self.local_thread_running { - return false; - } - - // TODO: an earlier out if world's archetypes did not change - for set_idx in conditions.sets_of_systems[system_index].difference(&self.evaluated_sets) { - for condition in &mut conditions.set_conditions[set_idx] { - condition.update_archetype_component_access(world); - if !condition - .archetype_component_access() - .is_compatible(&self.active_access) - { - return false; - } - } - } - - for condition in &mut conditions.system_conditions[system_index] { - condition.update_archetype_component_access(world); - if !condition - .archetype_component_access() - .is_compatible(&self.active_access) - { - return false; - } - } - - if !self.skipped_systems.contains(system_index) { - system.update_archetype_component_access(world); - if !system - .archetype_component_access() - .is_compatible(&self.active_access) - { - return false; - } - - // PERF: use an optimized clear() + extend() operation - let meta_access = - &mut self.system_task_metadata[system_index].archetype_component_access; - meta_access.clear(); - meta_access.extend(system.archetype_component_access()); - } - - true - } - - fn should_run( - &mut self, - system_index: usize, - _system: &BoxedSystem, - conditions: &mut Conditions, - world: &World, - ) -> bool { - #[cfg(feature = "trace")] - let _span = info_span!("check_conditions", name = &*_system.name()).entered(); - - let mut should_run = !self.skipped_systems.contains(system_index); - for set_idx in conditions.sets_of_systems[system_index].ones() { - if self.evaluated_sets.contains(set_idx) { - continue; - } - - // evaluate system set's conditions - let set_conditions_met = - evaluate_and_fold_conditions(&mut conditions.set_conditions[set_idx], world); - - if !set_conditions_met { - self.skipped_systems - .union_with(&conditions.systems_in_sets[set_idx]); - } - - should_run &= set_conditions_met; - self.evaluated_sets.insert(set_idx); - } - - // evaluate system's conditions - let system_conditions_met = - evaluate_and_fold_conditions(&mut conditions.system_conditions[system_index], world); - - if !system_conditions_met { - self.skipped_systems.insert(system_index); - } - - should_run &= system_conditions_met; - - should_run - } - - /// # Safety - /// Caller must not alias systems that are running. - unsafe fn spawn_system_task<'scope>( - &mut self, - scope: &Scope<'_, 'scope, ()>, - system_index: usize, - systems: &'scope [SyncUnsafeCell], - world: &'scope World, - ) { - // SAFETY: this system is not running, no other reference exists - let system = unsafe { &mut *systems[system_index].get() }; - - #[cfg(feature = "trace")] - let task_span = info_span!("system_task", name = &*system.name()); - #[cfg(feature = "trace")] - let system_span = info_span!("system", name = &*system.name()); - - let sender = self.sender.clone(); - let task = async move { - #[cfg(feature = "trace")] - let system_guard = system_span.enter(); - // SAFETY: access is compatible - unsafe { system.run_unsafe((), world) }; - #[cfg(feature = "trace")] - drop(system_guard); - sender - .send(system_index) - .await - .unwrap_or_else(|error| unreachable!("{}", error)); - }; - - #[cfg(feature = "trace")] - let task = task.instrument(task_span); - - let system_meta = &self.system_task_metadata[system_index]; - self.active_access - .extend(&system_meta.archetype_component_access); - - if system_meta.is_send { - scope.spawn(task); - } else { - self.local_thread_running = true; - scope.spawn_on_scope(task); - } - } - - /// # Safety - /// Caller must ensure no systems are currently borrowed. - unsafe fn spawn_exclusive_system_task<'scope>( - &mut self, - scope: &Scope<'_, 'scope, ()>, - system_index: usize, - systems: &'scope [SyncUnsafeCell], - world: &'scope mut World, - ) { - // SAFETY: this system is not running, no other reference exists - let system = unsafe { &mut *systems[system_index].get() }; - - #[cfg(feature = "trace")] - let task_span = info_span!("system_task", name = &*system.name()); - #[cfg(feature = "trace")] - let system_span = info_span!("system", name = &*system.name()); - - let sender = self.sender.clone(); - if is_apply_system_buffers(system) { - // TODO: avoid allocation - let mut unapplied_systems = self.unapplied_systems.clone(); - let task = async move { - #[cfg(feature = "trace")] - let system_guard = system_span.enter(); - apply_system_buffers(&mut unapplied_systems, systems, world); - #[cfg(feature = "trace")] - drop(system_guard); - sender - .send(system_index) - .await - .unwrap_or_else(|error| unreachable!("{}", error)); - }; - - #[cfg(feature = "trace")] - let task = task.instrument(task_span); - scope.spawn_on_scope(task); - } else { - let task = async move { - #[cfg(feature = "trace")] - let system_guard = system_span.enter(); - system.run((), world); - #[cfg(feature = "trace")] - drop(system_guard); - sender - .send(system_index) - .await - .unwrap_or_else(|error| unreachable!("{}", error)); - }; - - #[cfg(feature = "trace")] - let task = task.instrument(task_span); - scope.spawn_on_scope(task); - } - - self.exclusive_running = true; - self.local_thread_running = true; - } - - fn finish_system_and_signal_dependents(&mut self, system_index: usize) { - if self.system_task_metadata[system_index].is_exclusive { - self.exclusive_running = false; - } - - if !self.system_task_metadata[system_index].is_send { - self.local_thread_running = false; - } - - debug_assert!(self.num_running_systems >= 1); - self.num_running_systems -= 1; - self.num_completed_systems += 1; - self.running_systems.set(system_index, false); - self.completed_systems.insert(system_index); - self.unapplied_systems.insert(system_index); - self.signal_dependents(system_index); - } - - fn skip_system_and_signal_dependents(&mut self, system_index: usize) { - self.num_completed_systems += 1; - self.completed_systems.insert(system_index); - self.signal_dependents(system_index); - } - - fn signal_dependents(&mut self, system_index: usize) { - #[cfg(feature = "trace")] - let _span = info_span!("signal_dependents").entered(); - for &dep_idx in &self.system_task_metadata[system_index].dependents { - let remaining = &mut self.num_dependencies_remaining[dep_idx]; - debug_assert!(*remaining >= 1); - *remaining -= 1; - if *remaining == 0 && !self.completed_systems.contains(dep_idx) { - self.ready_systems.insert(dep_idx); - } - } - } - - fn rebuild_active_access(&mut self) { - self.active_access.clear(); - for index in self.running_systems.ones() { - let system_meta = &self.system_task_metadata[index]; - self.active_access - .extend(&system_meta.archetype_component_access); - } - } -} - -fn apply_system_buffers( - unapplied_systems: &mut FixedBitSet, - systems: &[SyncUnsafeCell], - world: &mut World, -) { - for system_index in unapplied_systems.ones() { - // SAFETY: none of these systems are running, no other references exist - let system = unsafe { &mut *systems[system_index].get() }; - #[cfg(feature = "trace")] - let _apply_buffers_span = info_span!("apply_buffers", name = &*system.name()).entered(); - system.apply_buffers(world); - } - - unapplied_systems.clear(); -} - -fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &World) -> bool { - // not short-circuiting is intentional - #[allow(clippy::unnecessary_fold)] - conditions - .iter_mut() - .map(|condition| { - #[cfg(feature = "trace")] - let _condition_span = info_span!("condition", name = &*condition.name()).entered(); - // SAFETY: caller ensures system access is compatible - unsafe { condition.run_unsafe((), world) } - }) - .fold(true, |acc, res| acc && res) -} diff --git a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs deleted file mode 100644 index 1d45aa29129b3..0000000000000 --- a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs +++ /dev/null @@ -1,111 +0,0 @@ -#[cfg(feature = "trace")] -use bevy_utils::tracing::info_span; -use fixedbitset::FixedBitSet; - -use crate::{ - schedule_v3::{BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule}, - world::World, -}; - -/// A variant of [`SingleThreadedExecutor`](crate::schedule_v3::SingleThreadedExecutor) that calls -/// [`apply_buffers`](crate::system::System::apply_buffers) immediately after running each system. -#[derive(Default)] -pub struct SimpleExecutor { - /// Systems sets whose conditions have been evaluated. - evaluated_sets: FixedBitSet, - /// Systems that have run or been skipped. - completed_systems: FixedBitSet, -} - -impl SystemExecutor for SimpleExecutor { - fn kind(&self) -> ExecutorKind { - ExecutorKind::Simple - } - - fn init(&mut self, schedule: &SystemSchedule) { - let sys_count = schedule.system_ids.len(); - let set_count = schedule.set_ids.len(); - self.evaluated_sets = FixedBitSet::with_capacity(set_count); - self.completed_systems = FixedBitSet::with_capacity(sys_count); - } - - fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) { - for system_index in 0..schedule.systems.len() { - #[cfg(feature = "trace")] - let name = schedule.systems[system_index].name(); - #[cfg(feature = "trace")] - let should_run_span = info_span!("check_conditions", name = &*name).entered(); - - let mut should_run = !self.completed_systems.contains(system_index); - for set_idx in schedule.sets_of_systems[system_index].ones() { - if self.evaluated_sets.contains(set_idx) { - continue; - } - - // evaluate system set's conditions - let set_conditions_met = - evaluate_and_fold_conditions(&mut schedule.set_conditions[set_idx], world); - - if !set_conditions_met { - self.completed_systems - .union_with(&schedule.systems_in_sets[set_idx]); - } - - should_run &= set_conditions_met; - self.evaluated_sets.insert(set_idx); - } - - // evaluate system's conditions - let system_conditions_met = - evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world); - - should_run &= system_conditions_met; - - #[cfg(feature = "trace")] - should_run_span.exit(); - - // system has either been skipped or will run - self.completed_systems.insert(system_index); - - if !should_run { - continue; - } - - let system = &mut schedule.systems[system_index]; - #[cfg(feature = "trace")] - let system_span = info_span!("system", name = &*name).entered(); - system.run((), world); - #[cfg(feature = "trace")] - system_span.exit(); - - #[cfg(feature = "trace")] - let _apply_buffers_span = info_span!("apply_buffers", name = &*name).entered(); - system.apply_buffers(world); - } - - self.evaluated_sets.clear(); - self.completed_systems.clear(); - } -} - -impl SimpleExecutor { - pub const fn new() -> Self { - Self { - evaluated_sets: FixedBitSet::new(), - completed_systems: FixedBitSet::new(), - } - } -} - -fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut World) -> bool { - // not short-circuiting is intentional - #[allow(clippy::unnecessary_fold)] - conditions - .iter_mut() - .map(|condition| { - #[cfg(feature = "trace")] - let _condition_span = info_span!("condition", name = &*condition.name()).entered(); - condition.run((), world) - }) - .fold(true, |acc, res| acc && res) -} diff --git a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs deleted file mode 100644 index 289b05b8c1e78..0000000000000 --- a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs +++ /dev/null @@ -1,137 +0,0 @@ -#[cfg(feature = "trace")] -use bevy_utils::tracing::info_span; -use fixedbitset::FixedBitSet; - -use crate::{ - schedule_v3::{ - is_apply_system_buffers, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule, - }, - world::World, -}; - -/// Runs the schedule using a single thread. -/// -/// Useful if you're dealing with a single-threaded environment, saving your threads for -/// other things, or just trying minimize overhead. -#[derive(Default)] -pub struct SingleThreadedExecutor { - /// System sets whose conditions have been evaluated. - evaluated_sets: FixedBitSet, - /// Systems that have run or been skipped. - completed_systems: FixedBitSet, - /// Systems that have run but have not had their buffers applied. - unapplied_systems: FixedBitSet, -} - -impl SystemExecutor for SingleThreadedExecutor { - fn kind(&self) -> ExecutorKind { - ExecutorKind::SingleThreaded - } - - fn init(&mut self, schedule: &SystemSchedule) { - // pre-allocate space - let sys_count = schedule.system_ids.len(); - let set_count = schedule.set_ids.len(); - self.evaluated_sets = FixedBitSet::with_capacity(set_count); - self.completed_systems = FixedBitSet::with_capacity(sys_count); - self.unapplied_systems = FixedBitSet::with_capacity(sys_count); - } - - fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) { - for system_index in 0..schedule.systems.len() { - #[cfg(feature = "trace")] - let name = schedule.systems[system_index].name(); - #[cfg(feature = "trace")] - let should_run_span = info_span!("check_conditions", name = &*name).entered(); - - let mut should_run = !self.completed_systems.contains(system_index); - for set_idx in schedule.sets_of_systems[system_index].ones() { - if self.evaluated_sets.contains(set_idx) { - continue; - } - - // evaluate system set's conditions - let set_conditions_met = - evaluate_and_fold_conditions(&mut schedule.set_conditions[set_idx], world); - - if !set_conditions_met { - self.completed_systems - .union_with(&schedule.systems_in_sets[set_idx]); - } - - should_run &= set_conditions_met; - self.evaluated_sets.insert(set_idx); - } - - // evaluate system's conditions - let system_conditions_met = - evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world); - - should_run &= system_conditions_met; - - #[cfg(feature = "trace")] - should_run_span.exit(); - - // system has either been skipped or will run - self.completed_systems.insert(system_index); - - if !should_run { - continue; - } - - let system = &mut schedule.systems[system_index]; - if is_apply_system_buffers(system) { - #[cfg(feature = "trace")] - let system_span = info_span!("system", name = &*name).entered(); - self.apply_system_buffers(schedule, world); - #[cfg(feature = "trace")] - system_span.exit(); - } else { - #[cfg(feature = "trace")] - let system_span = info_span!("system", name = &*name).entered(); - system.run((), world); - #[cfg(feature = "trace")] - system_span.exit(); - self.unapplied_systems.insert(system_index); - } - } - - self.apply_system_buffers(schedule, world); - self.evaluated_sets.clear(); - self.completed_systems.clear(); - } -} - -impl SingleThreadedExecutor { - pub const fn new() -> Self { - Self { - evaluated_sets: FixedBitSet::new(), - completed_systems: FixedBitSet::new(), - unapplied_systems: FixedBitSet::new(), - } - } - - fn apply_system_buffers(&mut self, schedule: &mut SystemSchedule, world: &mut World) { - for system_index in self.unapplied_systems.ones() { - let system = &mut schedule.systems[system_index]; - #[cfg(feature = "trace")] - let _apply_buffers_span = info_span!("apply_buffers", name = &*system.name()).entered(); - system.apply_buffers(world); - } - - self.unapplied_systems.clear(); - } -} - -fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut World) -> bool { - // not short-circuiting is intentional - #[allow(clippy::unnecessary_fold)] - conditions - .iter_mut() - .map(|condition| { - #[cfg(feature = "trace")] - let _condition_span = info_span!("condition", name = &*condition.name()).entered(); - condition.run((), world) - }) - .fold(true, |acc, res| acc && res) -} diff --git a/crates/bevy_ecs/src/schedule_v3/graph_utils.rs b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs deleted file mode 100644 index b58bad317a959..0000000000000 --- a/crates/bevy_ecs/src/schedule_v3/graph_utils.rs +++ /dev/null @@ -1,233 +0,0 @@ -use std::fmt::Debug; - -use bevy_utils::{ - petgraph::{graphmap::NodeTrait, prelude::*}, - HashMap, HashSet, -}; -use fixedbitset::FixedBitSet; - -use crate::schedule_v3::set::*; - -/// Unique identifier for a system or system set. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(crate) enum NodeId { - System(usize), - Set(usize), -} - -impl NodeId { - /// Returns the internal integer value. - pub fn index(&self) -> usize { - match self { - NodeId::System(index) | NodeId::Set(index) => *index, - } - } - - /// Returns `true` if the identified node is a system. - pub const fn is_system(&self) -> bool { - matches!(self, NodeId::System(_)) - } - - /// Returns `true` if the identified node is a system set. - pub const fn is_set(&self) -> bool { - matches!(self, NodeId::Set(_)) - } -} - -/// Specifies what kind of edge should be added to the dependency graph. -#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub(crate) enum DependencyKind { - /// A node that should be preceded. - Before, - /// A node that should be succeeded. - After, -} - -/// An edge to be added to the dependency graph. -#[derive(Clone)] -pub(crate) struct Dependency { - pub(crate) kind: DependencyKind, - pub(crate) set: BoxedSystemSet, -} - -impl Dependency { - pub fn new(kind: DependencyKind, set: BoxedSystemSet) -> Self { - Self { kind, set } - } -} - -/// Configures ambiguity detection for a single system. -#[derive(Clone, Debug, Default)] -pub(crate) enum Ambiguity { - #[default] - Check, - /// Ignore warnings with systems in any of these system sets. May contain duplicates. - IgnoreWithSet(Vec), - /// Ignore all warnings. - IgnoreAll, -} - -#[derive(Clone)] -pub(crate) struct GraphInfo { - pub(crate) sets: Vec, - pub(crate) dependencies: Vec, - pub(crate) ambiguous_with: Ambiguity, -} - -/// Converts 2D row-major pair of indices into a 1D array index. -pub(crate) fn index(row: usize, col: usize, num_cols: usize) -> usize { - debug_assert!(col < num_cols); - (row * num_cols) + col -} - -/// Converts a 1D array index into a 2D row-major pair of indices. -pub(crate) fn row_col(index: usize, num_cols: usize) -> (usize, usize) { - (index / num_cols, index % num_cols) -} - -/// Stores the results of the graph analysis. -pub(crate) struct CheckGraphResults { - /// Boolean reachability matrix for the graph. - pub(crate) reachable: FixedBitSet, - /// Pairs of nodes that have a path connecting them. - pub(crate) connected: HashSet<(V, V)>, - /// Pairs of nodes that don't have a path connecting them. - pub(crate) disconnected: HashSet<(V, V)>, - /// Edges that are redundant because a longer path exists. - pub(crate) transitive_edges: Vec<(V, V)>, - /// Variant of the graph with no transitive edges. - pub(crate) transitive_reduction: DiGraphMap, - /// Variant of the graph with all possible transitive edges. - // TODO: this will very likely be used by "if-needed" ordering - #[allow(dead_code)] - pub(crate) transitive_closure: DiGraphMap, -} - -impl Default for CheckGraphResults { - fn default() -> Self { - Self { - reachable: FixedBitSet::new(), - connected: HashSet::new(), - disconnected: HashSet::new(), - transitive_edges: Vec::new(), - transitive_reduction: DiGraphMap::new(), - transitive_closure: DiGraphMap::new(), - } - } -} - -/// Processes a DAG and computes its: -/// - transitive reduction (along with the set of removed edges) -/// - transitive closure -/// - reachability matrix (as a bitset) -/// - pairs of nodes connected by a path -/// - pairs of nodes not connected by a path -/// -/// The algorithm implemented comes from -/// ["On the calculation of transitive reduction-closure of orders"][1] by Habib, Morvan and Rampon. -/// -/// [1]: https://doi.org/10.1016/0012-365X(93)90164-O -pub(crate) fn check_graph( - graph: &DiGraphMap, - topological_order: &[V], -) -> CheckGraphResults -where - V: NodeTrait + Debug, -{ - if graph.node_count() == 0 { - return CheckGraphResults::default(); - } - - let n = graph.node_count(); - - // build a copy of the graph where the nodes and edges appear in topsorted order - let mut map = HashMap::with_capacity(n); - let mut topsorted = DiGraphMap::::new(); - // iterate nodes in topological order - for (i, &node) in topological_order.iter().enumerate() { - map.insert(node, i); - topsorted.add_node(node); - // insert nodes as successors to their predecessors - for pred in graph.neighbors_directed(node, Direction::Incoming) { - topsorted.add_edge(pred, node, ()); - } - } - - let mut reachable = FixedBitSet::with_capacity(n * n); - let mut connected = HashSet::new(); - let mut disconnected = HashSet::new(); - - let mut transitive_edges = Vec::new(); - let mut transitive_reduction = DiGraphMap::::new(); - let mut transitive_closure = DiGraphMap::::new(); - - let mut visited = FixedBitSet::with_capacity(n); - - // iterate nodes in topological order - for node in topsorted.nodes() { - transitive_reduction.add_node(node); - transitive_closure.add_node(node); - } - - // iterate nodes in reverse topological order - for a in topsorted.nodes().rev() { - let index_a = *map.get(&a).unwrap(); - // iterate their successors in topological order - for b in topsorted.neighbors_directed(a, Direction::Outgoing) { - let index_b = *map.get(&b).unwrap(); - debug_assert!(index_a < index_b); - if !visited[index_b] { - // edge is not redundant - transitive_reduction.add_edge(a, b, ()); - transitive_closure.add_edge(a, b, ()); - reachable.insert(index(index_a, index_b, n)); - - let successors = transitive_closure - .neighbors_directed(b, Direction::Outgoing) - .collect::>(); - for c in successors { - let index_c = *map.get(&c).unwrap(); - debug_assert!(index_b < index_c); - if !visited[index_c] { - visited.insert(index_c); - transitive_closure.add_edge(a, c, ()); - reachable.insert(index(index_a, index_c, n)); - } - } - } else { - // edge is redundant - transitive_edges.push((a, b)); - } - } - - visited.clear(); - } - - // partition pairs of nodes into "connected by path" and "not connected by path" - for i in 0..(n - 1) { - // reachable is upper triangular because the nodes were topsorted - for index in index(i, i + 1, n)..=index(i, n - 1, n) { - let (a, b) = row_col(index, n); - let pair = (topological_order[a], topological_order[b]); - if reachable[index] { - connected.insert(pair); - } else { - disconnected.insert(pair); - } - } - } - - // fill diagonal (nodes reach themselves) - // for i in 0..n { - // reachable.set(index(i, i, n), true); - // } - - CheckGraphResults { - reachable, - connected, - disconnected, - transitive_edges, - transitive_reduction, - transitive_closure, - } -} diff --git a/crates/bevy_ecs/src/schedule_v3/migration.rs b/crates/bevy_ecs/src/schedule_v3/migration.rs deleted file mode 100644 index c4932c2fbca7c..0000000000000 --- a/crates/bevy_ecs/src/schedule_v3/migration.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::schedule_v3::*; -use crate::world::World; - -/// Temporary "stageless" `App` methods. -pub trait AppExt { - /// Sets the [`Schedule`] that will be modified by default when you call `App::add_system` - /// and similar methods. - /// - /// **Note:** This will create the schedule if it does not already exist. - fn set_default_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self; - /// Applies the function to the [`Schedule`] associated with `label`. - /// - /// **Note:** This will create the schedule if it does not already exist. - fn edit_schedule( - &mut self, - label: impl ScheduleLabel, - f: impl FnMut(&mut Schedule), - ) -> &mut Self; - /// Adds [`State`] and [`NextState`] resources, [`OnEnter`] and [`OnExit`] schedules - /// for each state variant, and an instance of [`apply_state_transition::`] in - /// \ so that transitions happen before `Update`. - fn add_state(&mut self) -> &mut Self; -} - -/// Temporary "stageless" [`World`] methods. -pub trait WorldExt { - /// Runs the [`Schedule`] associated with `label`. - fn run_schedule(&mut self, label: impl ScheduleLabel); -} - -impl WorldExt for World { - fn run_schedule(&mut self, label: impl ScheduleLabel) { - if let Some(mut schedule) = self.resource_mut::().remove(&label) { - schedule.run(self); - self.resource_mut::().insert(label, schedule); - } - } -} diff --git a/crates/bevy_ecs/src/schedule_v3/mod.rs b/crates/bevy_ecs/src/schedule_v3/mod.rs deleted file mode 100644 index 11828cbbe5fc0..0000000000000 --- a/crates/bevy_ecs/src/schedule_v3/mod.rs +++ /dev/null @@ -1,622 +0,0 @@ -mod condition; -mod config; -mod executor; -mod graph_utils; -mod migration; -mod schedule; -mod set; -mod state; - -pub use self::condition::*; -pub use self::config::*; -pub use self::executor::*; -use self::graph_utils::*; -pub use self::migration::*; -pub use self::schedule::*; -pub use self::set::*; -pub use self::state::*; - -#[cfg(test)] -mod tests { - use super::*; - use std::sync::atomic::{AtomicU32, Ordering}; - - pub use crate as bevy_ecs; - pub use crate::schedule_v3::{IntoSystemConfig, IntoSystemSetConfig, Schedule, SystemSet}; - pub use crate::system::{Res, ResMut}; - pub use crate::{prelude::World, system::Resource}; - - #[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] - enum TestSet { - A, - B, - C, - D, - X, - } - - #[derive(Resource, Default)] - struct SystemOrder(Vec); - - #[derive(Resource, Default)] - struct RunConditionBool(pub bool); - - #[derive(Resource, Default)] - struct Counter(pub AtomicU32); - - fn make_exclusive_system(tag: u32) -> impl FnMut(&mut World) { - move |world| world.resource_mut::().0.push(tag) - } - - fn make_function_system(tag: u32) -> impl FnMut(ResMut) { - move |mut resource: ResMut| resource.0.push(tag) - } - - fn named_system(mut resource: ResMut) { - resource.0.push(u32::MAX); - } - - fn named_exclusive_system(world: &mut World) { - world.resource_mut::().0.push(u32::MAX); - } - - fn counting_system(counter: Res) { - counter.0.fetch_add(1, Ordering::Relaxed); - } - - mod system_execution { - use super::*; - - #[test] - fn run_system() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule.add_system(make_function_system(0)); - schedule.run(&mut world); - - assert_eq!(world.resource::().0, vec![0]); - } - - #[test] - fn run_exclusive_system() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule.add_system(make_exclusive_system(0)); - schedule.run(&mut world); - - assert_eq!(world.resource::().0, vec![0]); - } - - #[test] - #[cfg(not(miri))] - fn parallel_execution() { - use bevy_tasks::{ComputeTaskPool, TaskPool}; - use std::sync::{Arc, Barrier}; - - let mut world = World::default(); - let mut schedule = Schedule::default(); - let thread_count = ComputeTaskPool::init(TaskPool::default).thread_num(); - - let barrier = Arc::new(Barrier::new(thread_count)); - - for _ in 0..thread_count { - let inner = barrier.clone(); - schedule.add_system(move || { - inner.wait(); - }); - } - - schedule.run(&mut world); - } - } - - mod system_ordering { - use super::*; - - #[test] - fn order_systems() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule.add_system(named_system); - schedule.add_system(make_function_system(1).before(named_system)); - schedule.add_system( - make_function_system(0) - .after(named_system) - .in_set(TestSet::A), - ); - schedule.run(&mut world); - - assert_eq!(world.resource::().0, vec![1, u32::MAX, 0]); - - world.insert_resource(SystemOrder::default()); - - assert_eq!(world.resource::().0, vec![]); - - // modify the schedule after it's been initialized and test ordering with sets - schedule.configure_set(TestSet::A.after(named_system)); - schedule.add_system( - make_function_system(3) - .before(TestSet::A) - .after(named_system), - ); - schedule.add_system(make_function_system(4).after(TestSet::A)); - schedule.run(&mut world); - - assert_eq!( - world.resource::().0, - vec![1, u32::MAX, 3, 0, 4] - ); - } - - #[test] - fn order_exclusive_systems() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule.add_system(named_exclusive_system); - schedule.add_system(make_exclusive_system(1).before(named_exclusive_system)); - schedule.add_system(make_exclusive_system(0).after(named_exclusive_system)); - schedule.run(&mut world); - - assert_eq!(world.resource::().0, vec![1, u32::MAX, 0]); - } - - #[test] - fn add_systems_correct_order() { - #[derive(Resource)] - struct X(Vec); - - let mut world = World::new(); - world.init_resource::(); - - let mut schedule = Schedule::new(); - schedule.add_systems( - ( - make_function_system(0), - make_function_system(1), - make_exclusive_system(2), - make_function_system(3), - ) - .chain(), - ); - - schedule.run(&mut world); - assert_eq!(world.resource::().0, vec![0, 1, 2, 3]); - } - } - - mod conditions { - use crate::change_detection::DetectChanges; - - use super::*; - - #[test] - fn system_with_condition() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - world.init_resource::(); - - schedule.add_system( - make_function_system(0).run_if(|condition: Res| condition.0), - ); - - schedule.run(&mut world); - assert_eq!(world.resource::().0, vec![]); - - world.resource_mut::().0 = true; - schedule.run(&mut world); - assert_eq!(world.resource::().0, vec![0]); - } - - #[test] - fn run_exclusive_system_with_condition() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - world.init_resource::(); - - schedule.add_system( - make_exclusive_system(0).run_if(|condition: Res| condition.0), - ); - - schedule.run(&mut world); - assert_eq!(world.resource::().0, vec![]); - - world.resource_mut::().0 = true; - schedule.run(&mut world); - assert_eq!(world.resource::().0, vec![0]); - } - - #[test] - fn multiple_conditions_on_system() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule.add_system(counting_system.run_if(|| false).run_if(|| false)); - schedule.add_system(counting_system.run_if(|| true).run_if(|| false)); - schedule.add_system(counting_system.run_if(|| false).run_if(|| true)); - schedule.add_system(counting_system.run_if(|| true).run_if(|| true)); - - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - } - - #[test] - fn multiple_conditions_on_system_sets() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule.configure_set(TestSet::A.run_if(|| false).run_if(|| false)); - schedule.add_system(counting_system.in_set(TestSet::A)); - schedule.configure_set(TestSet::B.run_if(|| true).run_if(|| false)); - schedule.add_system(counting_system.in_set(TestSet::B)); - schedule.configure_set(TestSet::C.run_if(|| false).run_if(|| true)); - schedule.add_system(counting_system.in_set(TestSet::C)); - schedule.configure_set(TestSet::D.run_if(|| true).run_if(|| true)); - schedule.add_system(counting_system.in_set(TestSet::D)); - - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - } - - #[test] - fn systems_nested_in_system_sets() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule.configure_set(TestSet::A.run_if(|| false)); - schedule.add_system(counting_system.in_set(TestSet::A).run_if(|| false)); - schedule.configure_set(TestSet::B.run_if(|| true)); - schedule.add_system(counting_system.in_set(TestSet::B).run_if(|| false)); - schedule.configure_set(TestSet::C.run_if(|| false)); - schedule.add_system(counting_system.in_set(TestSet::C).run_if(|| true)); - schedule.configure_set(TestSet::D.run_if(|| true)); - schedule.add_system(counting_system.in_set(TestSet::D).run_if(|| true)); - - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - } - - #[test] - fn system_conditions_and_change_detection() { - #[derive(Resource, Default)] - struct Bool2(pub bool); - - let mut world = World::default(); - world.init_resource::(); - world.init_resource::(); - world.init_resource::(); - let mut schedule = Schedule::default(); - - schedule.add_system( - counting_system - .run_if(|res1: Res| res1.is_changed()) - .run_if(|res2: Res| res2.is_changed()), - ); - - // both resource were just added. - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // nothing has changed - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // RunConditionBool has changed, but counting_system did not run - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // internal state for the bool2 run criteria was updated in the - // previous run, so system still does not run - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // internal state for bool2 was updated, so system still does not run - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // now check that it works correctly changing Bool2 first and then RunConditionBool - world.get_resource_mut::().unwrap().0 = false; - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 2); - } - - #[test] - fn system_set_conditions_and_change_detection() { - #[derive(Resource, Default)] - struct Bool2(pub bool); - - let mut world = World::default(); - world.init_resource::(); - world.init_resource::(); - world.init_resource::(); - let mut schedule = Schedule::default(); - - schedule.configure_set( - TestSet::A - .run_if(|res1: Res| res1.is_changed()) - .run_if(|res2: Res| res2.is_changed()), - ); - - schedule.add_system(counting_system.in_set(TestSet::A)); - - // both resource were just added. - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // nothing has changed - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // RunConditionBool has changed, but counting_system did not run - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // internal state for the bool2 run criteria was updated in the - // previous run, so system still does not run - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // internal state for bool2 was updated, so system still does not run - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // the system only runs when both are changed on the same run - world.get_resource_mut::().unwrap().0 = false; - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 2); - } - - #[test] - fn mixed_conditions_and_change_detection() { - #[derive(Resource, Default)] - struct Bool2(pub bool); - - let mut world = World::default(); - world.init_resource::(); - world.init_resource::(); - world.init_resource::(); - let mut schedule = Schedule::default(); - - schedule - .configure_set(TestSet::A.run_if(|res1: Res| res1.is_changed())); - - schedule.add_system( - counting_system - .run_if(|res2: Res| res2.is_changed()) - .in_set(TestSet::A), - ); - - // both resource were just added. - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // nothing has changed - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // RunConditionBool has changed, but counting_system did not run - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // we now only change bool2 and the system also should not run - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // internal state for the bool2 run criteria was updated in the - // previous run, so system still does not run - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - - // the system only runs when both are changed on the same run - world.get_resource_mut::().unwrap().0 = false; - world.get_resource_mut::().unwrap().0 = false; - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 2); - } - } - - mod schedule_build_errors { - use super::*; - - #[test] - #[should_panic] - fn dependency_loop() { - let mut schedule = Schedule::new(); - schedule.configure_set(TestSet::X.after(TestSet::X)); - } - - #[test] - fn dependency_cycle() { - let mut world = World::new(); - let mut schedule = Schedule::new(); - - schedule.configure_set(TestSet::A.after(TestSet::B)); - schedule.configure_set(TestSet::B.after(TestSet::A)); - - let result = schedule.initialize(&mut world); - assert!(matches!(result, Err(ScheduleBuildError::DependencyCycle))); - - fn foo() {} - fn bar() {} - - let mut world = World::new(); - let mut schedule = Schedule::new(); - - schedule.add_systems((foo.after(bar), bar.after(foo))); - let result = schedule.initialize(&mut world); - assert!(matches!(result, Err(ScheduleBuildError::DependencyCycle))); - } - - #[test] - #[should_panic] - fn hierarchy_loop() { - let mut schedule = Schedule::new(); - schedule.configure_set(TestSet::X.in_set(TestSet::X)); - } - - #[test] - fn hierarchy_cycle() { - let mut world = World::new(); - let mut schedule = Schedule::new(); - - schedule.configure_set(TestSet::A.in_set(TestSet::B)); - schedule.configure_set(TestSet::B.in_set(TestSet::A)); - - let result = schedule.initialize(&mut world); - assert!(matches!(result, Err(ScheduleBuildError::HierarchyCycle))); - } - - #[test] - fn system_type_set_ambiguity() { - // Define some systems. - fn foo() {} - fn bar() {} - - let mut world = World::new(); - let mut schedule = Schedule::new(); - - // Schedule `bar` to run after `foo`. - schedule.add_system(foo); - schedule.add_system(bar.after(foo)); - - // There's only one `foo`, so it's fine. - let result = schedule.initialize(&mut world); - assert!(result.is_ok()); - - // Schedule another `foo`. - schedule.add_system(foo); - - // When there are multiple instances of `foo`, dependencies on - // `foo` are no longer allowed. Too much ambiguity. - let result = schedule.initialize(&mut world); - assert!(matches!( - result, - Err(ScheduleBuildError::SystemTypeSetAmbiguity(_)) - )); - - // same goes for `ambiguous_with` - let mut schedule = Schedule::new(); - schedule.add_system(foo); - schedule.add_system(bar.ambiguous_with(foo)); - let result = schedule.initialize(&mut world); - assert!(result.is_ok()); - schedule.add_system(foo); - let result = schedule.initialize(&mut world); - assert!(matches!( - result, - Err(ScheduleBuildError::SystemTypeSetAmbiguity(_)) - )); - } - - #[test] - #[should_panic] - fn in_system_type_set() { - fn foo() {} - fn bar() {} - - let mut schedule = Schedule::new(); - schedule.add_system(foo.in_set(bar.into_system_set())); - } - - #[test] - #[should_panic] - fn configure_system_type_set() { - fn foo() {} - let mut schedule = Schedule::new(); - schedule.configure_set(foo.into_system_set()); - } - - #[test] - fn hierarchy_redundancy() { - let mut world = World::new(); - let mut schedule = Schedule::new(); - - schedule.set_build_settings( - ScheduleBuildSettings::new().with_hierarchy_detection(LogLevel::Error), - ); - - // Add `A`. - schedule.configure_set(TestSet::A); - - // Add `B` as child of `A`. - schedule.configure_set(TestSet::B.in_set(TestSet::A)); - - // Add `X` as child of both `A` and `B`. - schedule.configure_set(TestSet::X.in_set(TestSet::A).in_set(TestSet::B)); - - // `X` cannot be the `A`'s child and grandchild at the same time. - let result = schedule.initialize(&mut world); - assert!(matches!( - result, - Err(ScheduleBuildError::HierarchyRedundancy) - )); - } - - #[test] - fn cross_dependency() { - let mut world = World::new(); - let mut schedule = Schedule::new(); - - // Add `B` and give it both kinds of relationships with `A`. - schedule.configure_set(TestSet::B.in_set(TestSet::A)); - schedule.configure_set(TestSet::B.after(TestSet::A)); - let result = schedule.initialize(&mut world); - assert!(matches!( - result, - Err(ScheduleBuildError::CrossDependency(_, _)) - )); - } - - #[test] - fn ambiguity() { - #[derive(Resource)] - struct X; - - fn res_ref(_x: Res) {} - fn res_mut(_x: ResMut) {} - - let mut world = World::new(); - let mut schedule = Schedule::new(); - - schedule.set_build_settings( - ScheduleBuildSettings::new().with_ambiguity_detection(LogLevel::Error), - ); - - schedule.add_systems((res_ref, res_mut)); - let result = schedule.initialize(&mut world); - assert!(matches!(result, Err(ScheduleBuildError::Ambiguity))); - } - } -} diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs deleted file mode 100644 index 2bd7b00eca72f..0000000000000 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ /dev/null @@ -1,1099 +0,0 @@ -use std::{ - fmt::{Debug, Write}, - result::Result, -}; - -use bevy_utils::default; -#[cfg(feature = "trace")] -use bevy_utils::tracing::info_span; -use bevy_utils::{ - petgraph::{algo::tarjan_scc, prelude::*}, - thiserror::Error, - tracing::{error, info, warn}, - HashMap, HashSet, -}; - -use fixedbitset::FixedBitSet; - -use crate::{ - self as bevy_ecs, - component::ComponentId, - schedule_v3::*, - system::{BoxedSystem, Resource}, - world::World, -}; - -/// Resource that stores [`Schedule`]s mapped to [`ScheduleLabel`]s. -#[derive(Default, Resource)] -pub struct Schedules { - inner: HashMap, -} - -impl Schedules { - /// Constructs an empty `Schedules` with zero initial capacity. - pub fn new() -> Self { - Self { - inner: HashMap::new(), - } - } - - /// Inserts a labeled schedule into the map. - /// - /// If the map already had an entry for `label`, `schedule` is inserted, - /// and the old schedule is returned. Otherwise, `None` is returned. - pub fn insert(&mut self, label: impl ScheduleLabel, schedule: Schedule) -> Option { - let label: Box = Box::new(label); - if self.inner.contains_key(&label) { - warn!("schedule with label {:?} already exists", label); - } - self.inner.insert(label, schedule) - } - - /// Removes the schedule corresponding to the `label` from the map, returning it if it existed. - pub fn remove(&mut self, label: &dyn ScheduleLabel) -> Option { - if !self.inner.contains_key(label) { - warn!("schedule with label {:?} not found", label); - } - self.inner.remove(label) - } - - /// Returns a reference to the schedule associated with `label`, if it exists. - pub fn get(&self, label: &dyn ScheduleLabel) -> Option<&Schedule> { - self.inner.get(label) - } - - /// Returns a mutable reference to the schedule associated with `label`, if it exists. - pub fn get_mut(&mut self, label: &dyn ScheduleLabel) -> Option<&mut Schedule> { - self.inner.get_mut(label) - } - - /// Iterates the change ticks of all systems in all stored schedules and clamps any older than - /// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE). - /// This prevents overflow and thus prevents false positives. - pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { - #[cfg(feature = "trace")] - let _all_span = info_span!("check stored schedule ticks").entered(); - // label used when trace feature is enabled - #[allow(unused_variables)] - for (label, schedule) in self.inner.iter_mut() { - #[cfg(feature = "trace")] - let name = format!("{:?}", label); - #[cfg(feature = "trace")] - let _one_span = info_span!("check schedule ticks", name = &name).entered(); - schedule.check_change_ticks(change_tick); - } - } -} - -/// A collection of systems, and the metadata and executor needed to run them -/// in a certain order under certain conditions. -pub struct Schedule { - graph: ScheduleGraph, - executable: SystemSchedule, - executor: Box, - executor_initialized: bool, -} - -impl Default for Schedule { - fn default() -> Self { - Self::new() - } -} - -impl Schedule { - /// Constructs an empty `Schedule`. - pub fn new() -> Self { - Self { - graph: ScheduleGraph::new(), - executable: SystemSchedule::new(), - executor: Box::new(MultiThreadedExecutor::new()), - executor_initialized: false, - } - } - - /// Add a system to the schedule. - pub fn add_system

(&mut self, system: impl IntoSystemConfig

) -> &mut Self { - self.graph.add_system(system); - self - } - - /// Add a collection of systems to the schedule. - pub fn add_systems

(&mut self, systems: impl IntoSystemConfigs

) -> &mut Self { - self.graph.add_systems(systems); - self - } - - /// Configure a system set in this schedule. - pub fn configure_set(&mut self, set: impl IntoSystemSetConfig) -> &mut Self { - self.graph.configure_set(set); - self - } - - /// Configure a collection of system sets in this schedule. - pub fn configure_sets(&mut self, sets: impl IntoSystemSetConfigs) -> &mut Self { - self.graph.configure_sets(sets); - self - } - - /// Changes the system set that new systems and system sets will join by default - /// if they aren't already part of one. - pub fn set_default_set(&mut self, set: impl SystemSet) -> &mut Self { - self.graph.set_default_set(set); - self - } - - /// Changes miscellaneous build settings. - pub fn set_build_settings(&mut self, settings: ScheduleBuildSettings) -> &mut Self { - self.graph.settings = settings; - self - } - - /// Returns the schedule's current execution strategy. - pub fn get_executor_kind(&self) -> ExecutorKind { - self.executor.kind() - } - - /// Sets the schedule's execution strategy. - pub fn set_executor_kind(&mut self, executor: ExecutorKind) -> &mut Self { - if executor != self.executor.kind() { - self.executor = match executor { - ExecutorKind::Simple => Box::new(SimpleExecutor::new()), - ExecutorKind::SingleThreaded => Box::new(SingleThreadedExecutor::new()), - ExecutorKind::MultiThreaded => Box::new(MultiThreadedExecutor::new()), - }; - self.executor_initialized = false; - } - self - } - - /// Runs all systems in this schedule on the `world`, using its current execution strategy. - pub fn run(&mut self, world: &mut World) { - world.check_change_ticks(); - self.initialize(world).unwrap(); - // TODO: label - #[cfg(feature = "trace")] - let _span = info_span!("schedule").entered(); - self.executor.run(&mut self.executable, world); - } - - /// Initializes any newly-added systems and conditions, rebuilds the executable schedule, - /// and re-initializes the executor. - pub fn initialize(&mut self, world: &mut World) -> Result<(), ScheduleBuildError> { - if self.graph.changed { - self.graph.initialize(world); - self.graph.update_schedule(&mut self.executable)?; - self.graph.changed = false; - self.executor_initialized = false; - } - - if !self.executor_initialized { - self.executor.init(&self.executable); - self.executor_initialized = true; - } - - Ok(()) - } - - /// Iterates the change ticks of all systems in the schedule and clamps any older than - /// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE). - /// This prevents overflow and thus prevents false positives. - pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { - for system in &mut self.executable.systems { - system.check_change_tick(change_tick); - } - - for conditions in &mut self.executable.system_conditions { - for system in conditions.iter_mut() { - system.check_change_tick(change_tick); - } - } - - for conditions in &mut self.executable.set_conditions { - for system in conditions.iter_mut() { - system.check_change_tick(change_tick); - } - } - } -} - -/// A directed acylic graph structure. -#[derive(Default)] -struct Dag { - /// A directed graph. - graph: DiGraphMap, - /// A cached topological ordering of the graph. - topsort: Vec, -} - -impl Dag { - fn new() -> Self { - Self { - graph: DiGraphMap::new(), - topsort: Vec::new(), - } - } -} - -/// A [`SystemSet`] with metadata, stored in a [`ScheduleGraph`]. -struct SystemSetNode { - inner: BoxedSystemSet, - /// `true` if this system set was modified with `configure_set` - configured: bool, -} - -impl SystemSetNode { - pub fn new(set: BoxedSystemSet) -> Self { - Self { - inner: set, - configured: false, - } - } - - pub fn name(&self) -> String { - format!("{:?}", &self.inner) - } - - pub fn is_system_type(&self) -> bool { - self.inner.is_system_type() - } -} - -/// Metadata for a [`Schedule`]. -#[derive(Default)] -struct ScheduleGraph { - systems: Vec>, - system_conditions: Vec>>, - system_sets: Vec, - system_set_conditions: Vec>>, - system_set_ids: HashMap, - uninit: Vec<(NodeId, usize)>, - hierarchy: Dag, - dependency: Dag, - dependency_flattened: Dag, - ambiguous_with: UnGraphMap, - ambiguous_with_flattened: UnGraphMap, - ambiguous_with_all: HashSet, - default_set: Option, - changed: bool, - settings: ScheduleBuildSettings, -} - -impl ScheduleGraph { - pub fn new() -> Self { - Self { - systems: Vec::new(), - system_conditions: Vec::new(), - system_sets: Vec::new(), - system_set_conditions: Vec::new(), - system_set_ids: HashMap::new(), - uninit: Vec::new(), - hierarchy: Dag::new(), - dependency: Dag::new(), - dependency_flattened: Dag::new(), - ambiguous_with: UnGraphMap::new(), - ambiguous_with_flattened: UnGraphMap::new(), - ambiguous_with_all: HashSet::new(), - default_set: None, - changed: false, - settings: default(), - } - } - - fn set_default_set(&mut self, set: impl SystemSet) { - assert!( - !set.is_system_type(), - "adding arbitrary systems to a system type set is not allowed" - ); - self.default_set = Some(Box::new(set)); - } - - fn add_systems

(&mut self, systems: impl IntoSystemConfigs

) { - let SystemConfigs { systems, chained } = systems.into_configs(); - let mut system_iter = systems.into_iter(); - if chained { - let Some(prev) = system_iter.next() else { return }; - let mut prev_id = self.add_system_inner(prev).unwrap(); - for next in system_iter { - let next_id = self.add_system_inner(next).unwrap(); - self.dependency.graph.add_edge(prev_id, next_id, ()); - prev_id = next_id; - } - } else { - for system in system_iter { - self.add_system_inner(system).unwrap(); - } - } - } - - fn add_system

(&mut self, system: impl IntoSystemConfig

) { - self.add_system_inner(system).unwrap(); - } - - fn add_system_inner

( - &mut self, - system: impl IntoSystemConfig

, - ) -> Result { - let SystemConfig { - system, - mut graph_info, - conditions, - } = system.into_config(); - - let id = NodeId::System(self.systems.len()); - - if graph_info.sets.is_empty() { - if let Some(default) = self.default_set.as_ref() { - graph_info.sets.push(default.dyn_clone()); - } - } - - // graph updates are immediate - self.update_graphs(id, graph_info)?; - - // system init has to be deferred (need `&mut World`) - self.uninit.push((id, 0)); - self.systems.push(Some(system)); - self.system_conditions.push(Some(conditions)); - - Ok(id) - } - - fn configure_sets(&mut self, sets: impl IntoSystemSetConfigs) { - let SystemSetConfigs { sets, chained } = sets.into_configs(); - let mut set_iter = sets.into_iter(); - if chained { - let Some(prev) = set_iter.next() else { return }; - let mut prev_id = self.configure_set_inner(prev).unwrap(); - for next in set_iter { - let next_id = self.configure_set_inner(next).unwrap(); - self.dependency.graph.add_edge(prev_id, next_id, ()); - prev_id = next_id; - } - } else { - for set in set_iter { - self.configure_set_inner(set).unwrap(); - } - } - } - - fn configure_set(&mut self, set: impl IntoSystemSetConfig) { - self.configure_set_inner(set).unwrap(); - } - - fn configure_set_inner( - &mut self, - set: impl IntoSystemSetConfig, - ) -> Result { - let SystemSetConfig { - set, - mut graph_info, - mut conditions, - } = set.into_config(); - - let id = match self.system_set_ids.get(&set) { - Some(&id) => id, - None => self.add_set(set.dyn_clone()), - }; - - let meta = &mut self.system_sets[id.index()]; - let already_configured = std::mem::replace(&mut meta.configured, true); - - // a system set can be configured multiple times, so this "default check" - // should only happen the first time `configure_set` is called on it - if !already_configured && graph_info.sets.is_empty() { - if let Some(default) = self.default_set.as_ref() { - info!("adding system set `{:?}` to default: `{:?}`", set, default); - graph_info.sets.push(default.dyn_clone()); - } - } - - // graph updates are immediate - self.update_graphs(id, graph_info)?; - - // system init has to be deferred (need `&mut World`) - let system_set_conditions = - self.system_set_conditions[id.index()].get_or_insert_with(Vec::new); - self.uninit.push((id, system_set_conditions.len())); - system_set_conditions.append(&mut conditions); - - Ok(id) - } - - fn add_set(&mut self, set: BoxedSystemSet) -> NodeId { - let id = NodeId::Set(self.system_sets.len()); - self.system_sets.push(SystemSetNode::new(set.dyn_clone())); - self.system_set_conditions.push(None); - self.system_set_ids.insert(set, id); - id - } - - fn check_sets( - &mut self, - id: &NodeId, - graph_info: &GraphInfo, - ) -> Result<(), ScheduleBuildError> { - for set in &graph_info.sets { - match self.system_set_ids.get(set) { - Some(set_id) => { - if id == set_id { - return Err(ScheduleBuildError::HierarchyLoop(set.dyn_clone())); - } - } - None => { - self.add_set(set.dyn_clone()); - } - } - } - - Ok(()) - } - - fn check_edges( - &mut self, - id: &NodeId, - graph_info: &GraphInfo, - ) -> Result<(), ScheduleBuildError> { - for Dependency { kind: _, set } in &graph_info.dependencies { - match self.system_set_ids.get(set) { - Some(set_id) => { - if id == set_id { - return Err(ScheduleBuildError::DependencyLoop(set.dyn_clone())); - } - } - None => { - self.add_set(set.dyn_clone()); - } - } - } - - if let Ambiguity::IgnoreWithSet(ambiguous_with) = &graph_info.ambiguous_with { - for set in ambiguous_with { - if !self.system_set_ids.contains_key(set) { - self.add_set(set.dyn_clone()); - } - } - } - - Ok(()) - } - - fn update_graphs( - &mut self, - id: NodeId, - graph_info: GraphInfo, - ) -> Result<(), ScheduleBuildError> { - self.check_sets(&id, &graph_info)?; - self.check_edges(&id, &graph_info)?; - self.changed = true; - - let GraphInfo { - sets, - dependencies, - ambiguous_with, - } = graph_info; - - if !self.hierarchy.graph.contains_node(id) { - self.hierarchy.graph.add_node(id); - } - - for set in sets.into_iter().map(|set| self.system_set_ids[&set]) { - self.hierarchy.graph.add_edge(set, id, ()); - } - - if !self.dependency.graph.contains_node(id) { - self.dependency.graph.add_node(id); - } - - for (kind, set) in dependencies - .into_iter() - .map(|Dependency { kind, set }| (kind, self.system_set_ids[&set])) - { - let (lhs, rhs) = match kind { - DependencyKind::Before => (id, set), - DependencyKind::After => (set, id), - }; - self.dependency.graph.add_edge(lhs, rhs, ()); - } - - match ambiguous_with { - Ambiguity::Check => (), - Ambiguity::IgnoreWithSet(ambigous_with) => { - for set in ambigous_with - .into_iter() - .map(|set| self.system_set_ids[&set]) - { - self.ambiguous_with.add_edge(id, set, ()); - } - } - Ambiguity::IgnoreAll => { - self.ambiguous_with_all.insert(id); - } - } - - Ok(()) - } - - fn initialize(&mut self, world: &mut World) { - for (id, i) in self.uninit.drain(..) { - match id { - NodeId::System(index) => { - self.systems[index].as_mut().unwrap().initialize(world); - if let Some(v) = self.system_conditions[index].as_mut() { - for condition in v.iter_mut() { - condition.initialize(world); - } - } - } - NodeId::Set(index) => { - if let Some(v) = self.system_set_conditions[index].as_mut() { - for condition in v.iter_mut().skip(i) { - condition.initialize(world); - } - } - } - } - } - } - - fn build_schedule(&mut self) -> Result { - // check hierarchy for cycles - let hier_scc = tarjan_scc(&self.hierarchy.graph); - if self.contains_cycles(&hier_scc) { - self.report_cycles(&hier_scc); - return Err(ScheduleBuildError::HierarchyCycle); - } - - self.hierarchy.topsort = hier_scc.into_iter().flatten().rev().collect::>(); - - let hier_results = check_graph(&self.hierarchy.graph, &self.hierarchy.topsort); - if self.contains_hierarchy_conflicts(&hier_results.transitive_edges) { - self.report_hierarchy_conflicts(&hier_results.transitive_edges); - if matches!(self.settings.hierarchy_detection, LogLevel::Error) { - return Err(ScheduleBuildError::HierarchyRedundancy); - } - } - - // remove redundant edges - self.hierarchy.graph = hier_results.transitive_reduction; - - // check dependencies for cycles - let dep_scc = tarjan_scc(&self.dependency.graph); - if self.contains_cycles(&dep_scc) { - self.report_cycles(&dep_scc); - return Err(ScheduleBuildError::DependencyCycle); - } - - self.dependency.topsort = dep_scc.into_iter().flatten().rev().collect::>(); - - // nodes can have dependent XOR hierarchical relationship - let dep_results = check_graph(&self.dependency.graph, &self.dependency.topsort); - for &(a, b) in dep_results.connected.iter() { - if hier_results.connected.contains(&(a, b)) || hier_results.connected.contains(&(b, a)) - { - let name_a = self.get_node_name(&a); - let name_b = self.get_node_name(&b); - return Err(ScheduleBuildError::CrossDependency(name_a, name_b)); - } - } - - // map system sets to all their member systems - let mut systems_in_sets = HashMap::with_capacity(self.system_sets.len()); - // iterate in reverse topological order (bottom-up) - for &id in self.hierarchy.topsort.iter().rev() { - if id.is_system() { - continue; - } - - let set = id; - systems_in_sets.insert(set, Vec::new()); - - for child in self - .hierarchy - .graph - .neighbors_directed(set, Direction::Outgoing) - { - match child { - NodeId::System(_) => { - systems_in_sets.get_mut(&set).unwrap().push(child); - } - NodeId::Set(_) => { - let [sys, child_sys] = - systems_in_sets.get_many_mut([&set, &child]).unwrap(); - sys.extend_from_slice(child_sys); - } - } - } - } - - // can't depend on or be ambiguous with system type sets that have many instances - for (&set, systems) in systems_in_sets.iter() { - let node = &self.system_sets[set.index()]; - if node.is_system_type() { - let ambiguities = self.ambiguous_with.edges(set).count(); - let mut dependencies = 0; - dependencies += self - .dependency - .graph - .edges_directed(set, Direction::Incoming) - .count(); - dependencies += self - .dependency - .graph - .edges_directed(set, Direction::Outgoing) - .count(); - if systems.len() > 1 && (ambiguities > 0 || dependencies > 0) { - return Err(ScheduleBuildError::SystemTypeSetAmbiguity( - node.inner.dyn_clone(), - )); - } - } - } - - // flatten dependency graph - let mut dependency_flattened = DiGraphMap::new(); - for id in self.dependency.graph.nodes() { - if id.is_system() { - dependency_flattened.add_node(id); - } - } - - for (lhs, rhs, _) in self.dependency.graph.all_edges() { - match (lhs, rhs) { - (NodeId::System(_), NodeId::System(_)) => { - dependency_flattened.add_edge(lhs, rhs, ()); - } - (NodeId::Set(_), NodeId::System(_)) => { - for &lhs_ in &systems_in_sets[&lhs] { - dependency_flattened.add_edge(lhs_, rhs, ()); - } - } - (NodeId::System(_), NodeId::Set(_)) => { - for &rhs_ in &systems_in_sets[&rhs] { - dependency_flattened.add_edge(lhs, rhs_, ()); - } - } - (NodeId::Set(_), NodeId::Set(_)) => { - for &lhs_ in &systems_in_sets[&lhs] { - for &rhs_ in &systems_in_sets[&rhs] { - dependency_flattened.add_edge(lhs_, rhs_, ()); - } - } - } - } - } - - // check flattened dependencies for cycles - let flat_scc = tarjan_scc(&dependency_flattened); - if self.contains_cycles(&flat_scc) { - self.report_cycles(&flat_scc); - return Err(ScheduleBuildError::DependencyCycle); - } - - self.dependency_flattened.graph = dependency_flattened; - self.dependency_flattened.topsort = - flat_scc.into_iter().flatten().rev().collect::>(); - - let flat_results = check_graph( - &self.dependency_flattened.graph, - &self.dependency_flattened.topsort, - ); - - // remove redundant edges - self.dependency_flattened.graph = flat_results.transitive_reduction; - - // flatten allowed ambiguities - let mut ambiguous_with_flattened = UnGraphMap::new(); - for (lhs, rhs, _) in self.ambiguous_with.all_edges() { - match (lhs, rhs) { - (NodeId::System(_), NodeId::System(_)) => { - ambiguous_with_flattened.add_edge(lhs, rhs, ()); - } - (NodeId::Set(_), NodeId::System(_)) => { - for &lhs_ in &systems_in_sets[&lhs] { - ambiguous_with_flattened.add_edge(lhs_, rhs, ()); - } - } - (NodeId::System(_), NodeId::Set(_)) => { - for &rhs_ in &systems_in_sets[&rhs] { - ambiguous_with_flattened.add_edge(lhs, rhs_, ()); - } - } - (NodeId::Set(_), NodeId::Set(_)) => { - for &lhs_ in &systems_in_sets[&lhs] { - for &rhs_ in &systems_in_sets[&rhs] { - ambiguous_with_flattened.add_edge(lhs_, rhs_, ()); - } - } - } - } - } - - self.ambiguous_with_flattened = ambiguous_with_flattened; - - // check for conflicts - let mut conflicting_systems = Vec::new(); - for &(a, b) in flat_results.disconnected.iter() { - if self.ambiguous_with_flattened.contains_edge(a, b) - || self.ambiguous_with_all.contains(&a) - || self.ambiguous_with_all.contains(&b) - { - continue; - } - - let system_a = self.systems[a.index()].as_ref().unwrap(); - let system_b = self.systems[b.index()].as_ref().unwrap(); - if system_a.is_exclusive() || system_b.is_exclusive() { - conflicting_systems.push((a, b, Vec::new())); - } else { - let access_a = system_a.component_access(); - let access_b = system_b.component_access(); - if !access_a.is_compatible(access_b) { - let conflicts = access_a.get_conflicts(access_b); - conflicting_systems.push((a, b, conflicts)); - } - } - } - - if self.contains_conflicts(&conflicting_systems) { - self.report_conflicts(&conflicting_systems); - if matches!(self.settings.ambiguity_detection, LogLevel::Error) { - return Err(ScheduleBuildError::Ambiguity); - } - } - - // build the schedule - let dg_system_ids = self.dependency_flattened.topsort.clone(); - let dg_system_idx_map = dg_system_ids - .iter() - .cloned() - .enumerate() - .map(|(i, id)| (id, i)) - .collect::>(); - - let hg_systems = self - .hierarchy - .topsort - .iter() - .cloned() - .enumerate() - .filter(|&(_i, id)| id.is_system()) - .collect::>(); - - let (hg_set_idxs, hg_set_ids): (Vec<_>, Vec<_>) = self - .hierarchy - .topsort - .iter() - .cloned() - .enumerate() - .filter(|&(_i, id)| { - // ignore system sets that have no conditions - // ignore system type sets (already covered, they don't have conditions) - id.is_set() - && self.system_set_conditions[id.index()] - .as_ref() - .filter(|v| !v.is_empty()) - .is_some() - }) - .unzip(); - - let sys_count = self.systems.len(); - let set_count = hg_set_ids.len(); - let node_count = self.systems.len() + self.system_sets.len(); - - // get the number of dependencies and the immediate dependents of each system - // (needed by multi-threaded executor to run systems in the correct order) - let mut system_dependencies = Vec::with_capacity(sys_count); - let mut system_dependents = Vec::with_capacity(sys_count); - for &sys_id in &dg_system_ids { - let num_dependencies = self - .dependency_flattened - .graph - .neighbors_directed(sys_id, Direction::Incoming) - .count(); - - let dependents = self - .dependency_flattened - .graph - .neighbors_directed(sys_id, Direction::Outgoing) - .map(|dep_id| dg_system_idx_map[&dep_id]) - .collect::>(); - - system_dependencies.push(num_dependencies); - system_dependents.push(dependents); - } - - // get the rows and columns of the hierarchy graph's reachability matrix - // (needed to we can evaluate conditions in the correct order) - let mut systems_in_sets = vec![FixedBitSet::with_capacity(sys_count); set_count]; - for (i, &row) in hg_set_idxs.iter().enumerate() { - let bitset = &mut systems_in_sets[i]; - for &(col, sys_id) in &hg_systems { - let idx = dg_system_idx_map[&sys_id]; - let is_descendant = hier_results.reachable[index(row, col, node_count)]; - bitset.set(idx, is_descendant); - } - } - - let mut sets_of_systems = vec![FixedBitSet::with_capacity(set_count); sys_count]; - for &(col, sys_id) in &hg_systems { - let i = dg_system_idx_map[&sys_id]; - let bitset = &mut sets_of_systems[i]; - for (idx, &row) in hg_set_idxs - .iter() - .enumerate() - .take_while(|&(_idx, &row)| row < col) - { - let is_ancestor = hier_results.reachable[index(row, col, node_count)]; - bitset.set(idx, is_ancestor); - } - } - - Ok(SystemSchedule { - systems: Vec::with_capacity(sys_count), - system_conditions: Vec::with_capacity(sys_count), - set_conditions: Vec::with_capacity(set_count), - system_ids: dg_system_ids, - set_ids: hg_set_ids, - system_dependencies, - system_dependents, - sets_of_systems, - systems_in_sets, - }) - } - - fn update_schedule(&mut self, schedule: &mut SystemSchedule) -> Result<(), ScheduleBuildError> { - if !self.uninit.is_empty() { - return Err(ScheduleBuildError::Uninitialized); - } - - // move systems out of old schedule - for ((id, system), conditions) in schedule - .system_ids - .drain(..) - .zip(schedule.systems.drain(..)) - .zip(schedule.system_conditions.drain(..)) - { - self.systems[id.index()] = Some(system); - self.system_conditions[id.index()] = Some(conditions); - } - - for (id, conditions) in schedule - .set_ids - .drain(..) - .zip(schedule.set_conditions.drain(..)) - { - self.system_set_conditions[id.index()] = Some(conditions); - } - - *schedule = self.build_schedule()?; - - // move systems into new schedule - for &id in &schedule.system_ids { - let system = self.systems[id.index()].take().unwrap(); - let conditions = self.system_conditions[id.index()].take().unwrap(); - schedule.systems.push(system); - schedule.system_conditions.push(conditions); - } - - for &id in &schedule.set_ids { - let conditions = self.system_set_conditions[id.index()].take().unwrap(); - schedule.set_conditions.push(conditions); - } - - Ok(()) - } -} - -// methods for reporting errors -impl ScheduleGraph { - fn get_node_name(&self, id: &NodeId) -> String { - match id { - NodeId::System(_) => self.systems[id.index()] - .as_ref() - .unwrap() - .name() - .to_string(), - NodeId::Set(_) => self.system_sets[id.index()].name(), - } - } - - fn get_node_kind(id: &NodeId) -> &'static str { - match id { - NodeId::System(_) => "system", - NodeId::Set(_) => "system set", - } - } - - fn contains_hierarchy_conflicts(&self, transitive_edges: &[(NodeId, NodeId)]) -> bool { - if transitive_edges.is_empty() { - return false; - } - - true - } - - fn report_hierarchy_conflicts(&self, transitive_edges: &[(NodeId, NodeId)]) { - let mut message = String::from("hierarchy contains redundant edge(s)"); - for (parent, child) in transitive_edges { - writeln!( - message, - " -- {:?} '{:?}' cannot be child of set '{:?}', longer path exists", - Self::get_node_kind(child), - self.get_node_name(child), - self.get_node_name(parent), - ) - .unwrap(); - } - - error!("{}", message); - } - - fn contains_cycles(&self, strongly_connected_components: &[Vec]) -> bool { - if strongly_connected_components - .iter() - .all(|scc| scc.len() == 1) - { - return false; - } - - true - } - - fn report_cycles(&self, strongly_connected_components: &[Vec]) { - let components_with_cycles = strongly_connected_components - .iter() - .filter(|scc| scc.len() > 1) - .cloned() - .collect::>(); - - let mut message = format!( - "schedule contains at least {} cycle(s)", - components_with_cycles.len() - ); - - writeln!(message, " -- cycle(s) found within:").unwrap(); - for (i, scc) in components_with_cycles.into_iter().enumerate() { - let names = scc - .iter() - .map(|id| self.get_node_name(id)) - .collect::>(); - writeln!(message, " ---- {i}: {names:?}").unwrap(); - } - - error!("{}", message); - } - - fn contains_conflicts(&self, conflicts: &[(NodeId, NodeId, Vec)]) -> bool { - if conflicts.is_empty() { - return false; - } - - true - } - - fn report_conflicts(&self, ambiguities: &[(NodeId, NodeId, Vec)]) { - let mut string = String::from( - "Some systems with conflicting access have indeterminate execution order. \ - Consider adding `before`, `after`, or `ambiguous_with` relationships between these:\n", - ); - - for (system_a, system_b, conflicts) in ambiguities { - debug_assert!(system_a.is_system()); - debug_assert!(system_b.is_system()); - let name_a = self.get_node_name(system_a); - let name_b = self.get_node_name(system_b); - - writeln!(string, " -- {name_a} and {name_b}").unwrap(); - if !conflicts.is_empty() { - writeln!(string, " conflict on: {conflicts:?}").unwrap(); - } else { - // one or both systems must be exclusive - let world = std::any::type_name::(); - writeln!(string, " conflict on: {world}").unwrap(); - } - } - - warn!("{}", string); - } -} - -/// Category of errors encountered during schedule construction. -#[derive(Error, Debug)] -#[non_exhaustive] -pub enum ScheduleBuildError { - /// A system set contains itself. - #[error("`{0:?}` contains itself.")] - HierarchyLoop(BoxedSystemSet), - /// The hierarchy of system sets contains a cycle. - #[error("System set hierarchy contains cycle(s).")] - HierarchyCycle, - /// The hierarchy of system sets contains redundant edges. - /// - /// This error is disabled by default, but can be opted-in using [`ScheduleBuildSettings`]. - #[error("System set hierarchy contains redundant edges.")] - HierarchyRedundancy, - /// A system (set) has been told to run before itself. - #[error("`{0:?}` depends on itself.")] - DependencyLoop(BoxedSystemSet), - /// The dependency graph contains a cycle. - #[error("System dependencies contain cycle(s).")] - DependencyCycle, - /// Tried to order a system (set) relative to a system set it belongs to. - #[error("`{0:?}` and `{1:?}` have both `in_set` and `before`-`after` relationships (these might be transitive). This combination is unsolvable as a system cannot run before or after a set it belongs to.")] - CrossDependency(String, String), - /// Tried to order a system (set) relative to all instances of some system function. - #[error("Tried to order against `fn {0:?}` in a schedule that has more than one `{0:?}` instance. `fn {0:?}` is a `SystemTypeSet` and cannot be used for ordering if ambiguous. Use a different set without this restriction.")] - SystemTypeSetAmbiguity(BoxedSystemSet), - /// Systems with conflicting access have indeterminate run order. - /// - /// This error is disabled by default, but can be opted-in using [`ScheduleBuildSettings`]. - #[error("Systems with conflicting access have indeterminate run order.")] - Ambiguity, - /// Tried to run a schedule before all of its systems have been initialized. - #[error("Systems in schedule have not been initialized.")] - Uninitialized, -} - -/// Specifies how schedule construction should respond to detecting a certain kind of issue. -pub enum LogLevel { - /// Occurrences are logged only. - Warn, - /// Occurrences are logged and result in errors. - Error, -} - -/// Specifies miscellaneous settings for schedule construction. -pub struct ScheduleBuildSettings { - ambiguity_detection: LogLevel, - hierarchy_detection: LogLevel, -} - -impl Default for ScheduleBuildSettings { - fn default() -> Self { - Self::new() - } -} - -impl ScheduleBuildSettings { - pub const fn new() -> Self { - Self { - ambiguity_detection: LogLevel::Warn, - hierarchy_detection: LogLevel::Warn, - } - } - - /// Determines whether the presence of ambiguities (systems with conflicting access but indeterminate order) - /// is only logged or also results in an [`Ambiguity`](ScheduleBuildError::Ambiguity) error. - pub fn with_ambiguity_detection(mut self, level: LogLevel) -> Self { - self.ambiguity_detection = level; - self - } - - /// Determines whether the presence of redundant edges in the hierarchy of system sets is only - /// logged or also results in a [`HierarchyRedundancy`](ScheduleBuildError::HierarchyRedundancy) - /// error. - pub fn with_hierarchy_detection(mut self, level: LogLevel) -> Self { - self.hierarchy_detection = level; - self - } -} diff --git a/crates/bevy_ecs/src/schedule_v3/state.rs b/crates/bevy_ecs/src/schedule_v3/state.rs deleted file mode 100644 index 85f357c670f7f..0000000000000 --- a/crates/bevy_ecs/src/schedule_v3/state.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::fmt::Debug; -use std::hash::Hash; -use std::mem; - -use crate as bevy_ecs; -use crate::schedule_v3::{ScheduleLabel, SystemSet, WorldExt}; -use crate::system::Resource; -use crate::world::World; - -/// Types that can define states in a finite-state machine. -pub trait States: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug { - type Iter: Iterator; - - /// Returns an iterator over all the state variants. - fn states() -> Self::Iter; -} - -/// The label of a [`Schedule`](super::Schedule) that runs whenever [`State`] -/// enters this state. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] -pub struct OnEnter(pub S); - -/// The label of a [`Schedule`](super::Schedule) that runs whenever [`State`] -/// exits this state. -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] -pub struct OnExit(pub S); - -/// A [`SystemSet`] that will run within \ when this state is active. -/// -/// This is provided for convenience. A more general [`state_equals`](super::state_equals) -/// [condition](super::Condition) also exists for systems that need to run elsewhere. -#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] -pub struct OnUpdate(pub S); - -/// A finite-state machine whose transitions have associated schedules -/// ([`OnEnter(state)`] and [`OnExit(state)`]). -/// -/// The current state value can be accessed through this resource. To *change* the state, -/// queue a transition in the [`NextState`] resource, and it will be applied by the next -/// [`apply_state_transition::`] system. -#[derive(Resource)] -pub struct State(pub S); - -/// The next state of [`State`]. -/// -/// To queue a transition, just set the contained value to `Some(next_state)`. -#[derive(Resource)] -pub struct NextState(pub Option); - -/// If a new state is queued in [`NextState`], this system: -/// - Takes the new state value from [`NextState`] and updates [`State`]. -/// - Runs the [`OnExit(exited_state)`] schedule. -/// - Runs the [`OnEnter(entered_state)`] schedule. -pub fn apply_state_transition(world: &mut World) { - if world.resource::>().0.is_some() { - let entered_state = world.resource_mut::>().0.take().unwrap(); - let exited_state = mem::replace( - &mut world.resource_mut::>().0, - entered_state.clone(), - ); - world.run_schedule(OnExit(exited_state)); - world.run_schedule(OnEnter(entered_state)); - } -} From 6f7769515c721ad312ffa020ed4948298717e1f2 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 10:16:26 -0500 Subject: [PATCH 004/247] Remove broken imports --- crates/bevy_ecs/src/lib.rs | 5 +---- crates/bevy_ecs/src/system/exclusive_function_system.rs | 1 - crates/bevy_ecs/src/system/function_system.rs | 1 - crates/bevy_ecs/src/system/system.rs | 2 +- 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 47d93beab837d..e3a85e30ee2d6 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -33,10 +33,7 @@ pub mod prelude { entity::Entity, event::{EventReader, EventWriter, Events}, query::{Added, AnyOf, ChangeTrackers, Changed, Or, QueryState, With, Without}, - schedule::{ - IntoSystemDescriptor, RunCriteria, RunCriteriaDescriptorCoercion, RunCriteriaLabel, - Schedule, Stage, StageLabel, State, SystemLabel, SystemSet, SystemStage, - }, + schedule::{Schedule, State, SystemSet}, system::{ adapter as system_adapter, adapter::{dbg, error, ignore, info, unwrap, warn}, diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index 77dda93b30d17..e8eeb743eba03 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -3,7 +3,6 @@ use crate::{ change_detection::MAX_CHANGE_AGE, component::ComponentId, query::Access, - schedule::{SystemLabel, SystemLabelId}, system::{ check_system_change_tick, AsSystemLabel, ExclusiveSystemParam, ExclusiveSystemParamItem, In, InputMarker, IntoSystem, System, SystemMeta, SystemTypeIdLabel, diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 7f5b1ccf0c928..88252a745a040 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -4,7 +4,6 @@ use crate::{ component::ComponentId, prelude::FromWorld, query::{Access, FilteredAccessSet}, - schedule::{SystemLabel, SystemLabelId}, system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem}, world::{World, WorldId}, }; diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index e046bfc031539..f75782d260d29 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -3,7 +3,7 @@ use core::fmt::Debug; use crate::{ archetype::ArchetypeComponentId, change_detection::MAX_CHANGE_AGE, component::ComponentId, - query::Access, schedule::SystemLabelId, world::World, + query::Access, world::World, }; use std::any::TypeId; From a918ddbb19061588004aa4c93e222a9c7c3a64d3 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 10:17:32 -0500 Subject: [PATCH 005/247] Remove PipeSystem::defaultLabels in favor of sets --- crates/bevy_ecs/src/system/system_piping.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/bevy_ecs/src/system/system_piping.rs b/crates/bevy_ecs/src/system/system_piping.rs index 89cd87935cbfa..ca45cca7fb090 100644 --- a/crates/bevy_ecs/src/system/system_piping.rs +++ b/crates/bevy_ecs/src/system/system_piping.rs @@ -146,12 +146,6 @@ impl> System for PipeSystem< self.system_b.set_last_change_tick(last_change_tick); } - fn default_labels(&self) -> Vec { - let mut labels = self.system_a.default_labels(); - labels.extend(&self.system_b.default_labels()); - labels - } - fn default_system_sets(&self) -> Vec> { let mut system_sets = self.system_a.default_system_sets(); system_sets.extend_from_slice(&self.system_b.default_system_sets()); From 305caa4198499c7fc020b08016927c816979d19e Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 10:19:06 -0500 Subject: [PATCH 006/247] Fix path for schedule crate in macros and doc link --- crates/bevy_ecs/macros/src/lib.rs | 8 ++------ crates/bevy_ecs/src/schedule/executor/simple.rs | 2 +- crates/bevy_ecs/src/system/system.rs | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 07d01a8c870ab..7b7e87fd98c09 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -572,9 +572,7 @@ pub fn derive_run_criteria_label(input: TokenStream) -> TokenStream { pub fn derive_schedule_label(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let mut trait_path = bevy_ecs_path(); - trait_path - .segments - .push(format_ident!("schedule_v3").into()); + trait_path.segments.push(format_ident!("schedule").into()); trait_path .segments .push(format_ident!("ScheduleLabel").into()); @@ -586,9 +584,7 @@ pub fn derive_schedule_label(input: TokenStream) -> TokenStream { pub fn derive_system_set(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let mut trait_path = bevy_ecs_path(); - trait_path - .segments - .push(format_ident!("schedule_v3").into()); + trait_path.segments.push(format_ident!("schedule").into()); trait_path.segments.push(format_ident!("SystemSet").into()); derive_set(input, &trait_path) } diff --git a/crates/bevy_ecs/src/schedule/executor/simple.rs b/crates/bevy_ecs/src/schedule/executor/simple.rs index 6b68d0a35c923..89dd671765ce5 100644 --- a/crates/bevy_ecs/src/schedule/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule/executor/simple.rs @@ -7,7 +7,7 @@ use crate::{ world::World, }; -/// A variant of [`SingleThreadedExecutor`](crate::schedule_v3::SingleThreadedExecutor) that calls +/// A variant of [`SingleThreadedExecutor`](crate::schedule::SingleThreadedExecutor) that calls /// [`apply_buffers`](crate::system::System::apply_buffers) immediately after running each system. #[derive(Default)] pub struct SimpleExecutor { diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index f75782d260d29..9f25a38f67760 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -68,7 +68,7 @@ pub trait System: Send + Sync + 'static { fn default_labels(&self) -> Vec { Vec::new() } - /// Returns the system's default [system sets](crate::schedule_v3::SystemSet). + /// Returns the system's default [system sets](crate::schedule::SystemSet). fn default_system_sets(&self) -> Vec> { Vec::new() } From 71037691b865e10660765709117ab111f9a7ff12 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 10:22:21 -0500 Subject: [PATCH 007/247] Remove default_labels trait method --- crates/bevy_ecs/src/system/exclusive_function_system.rs | 4 ---- crates/bevy_ecs/src/system/function_system.rs | 4 ---- crates/bevy_ecs/src/system/system.rs | 4 ---- 3 files changed, 12 deletions(-) diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index e8eeb743eba03..c991bd57b8ba7 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -154,10 +154,6 @@ where ); } - fn default_labels(&self) -> Vec { - vec![self.func.as_system_label().as_label()] - } - fn default_system_sets(&self) -> Vec> { let set = crate::schedule::SystemTypeSet::::new(); vec![Box::new(set)] diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 88252a745a040..92ebf0c50e7de 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -522,10 +522,6 @@ where ); } - fn default_labels(&self) -> Vec { - vec![self.func.as_system_label().as_label()] - } - fn default_system_sets(&self) -> Vec> { let set = crate::schedule::SystemTypeSet::::new(); vec![Box::new(set)] diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 9f25a38f67760..37809dc6f8d7e 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -64,10 +64,6 @@ pub trait System: Send + Sync + 'static { /// Update the system's archetype component [`Access`]. fn update_archetype_component_access(&mut self, world: &World); fn check_change_tick(&mut self, change_tick: u32); - /// The default labels for the system - fn default_labels(&self) -> Vec { - Vec::new() - } /// Returns the system's default [system sets](crate::schedule::SystemSet). fn default_system_sets(&self) -> Vec> { Vec::new() From cd36bd349891d18c9626594f8629e86f5720e8a3 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 10:26:41 -0500 Subject: [PATCH 008/247] Restore label.rs --- crates/bevy_ecs/src/schedule/label.rs | 21 +++++++++++++++++++ crates/bevy_ecs/src/schedule/mod.rs | 2 ++ .../src/system/exclusive_function_system.rs | 1 + crates/bevy_ecs/src/system/function_system.rs | 1 + 4 files changed, 25 insertions(+) create mode 100644 crates/bevy_ecs/src/schedule/label.rs diff --git a/crates/bevy_ecs/src/schedule/label.rs b/crates/bevy_ecs/src/schedule/label.rs new file mode 100644 index 0000000000000..c2f4d1fbfab7e --- /dev/null +++ b/crates/bevy_ecs/src/schedule/label.rs @@ -0,0 +1,21 @@ +pub use bevy_ecs_macros::{RunCriteriaLabel, StageLabel, SystemLabel}; +use bevy_utils::define_label; + +define_label!( + /// A strongly-typed class of labels used to identify [`Stage`](crate::schedule::Stage)s. + StageLabel, + /// Strongly-typed identifier for a [`StageLabel`]. + StageLabelId, +); +define_label!( + /// A strongly-typed class of labels used to identify [`System`](crate::system::System)s. + SystemLabel, + /// Strongly-typed identifier for a [`SystemLabel`]. + SystemLabelId, +); +define_label!( + /// A strongly-typed class of labels used to identify [run criteria](crate::schedule::RunCriteria). + RunCriteriaLabel, + /// Strongly-typed identifier for a [`RunCriteriaLabel`]. + RunCriteriaLabelId, +); diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index 77690bd29f9b4..5580be138dd5e 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -2,6 +2,7 @@ mod condition; mod config; mod executor; mod graph_utils; +mod label; mod migration; mod schedule; mod set; @@ -11,6 +12,7 @@ pub use self::condition::*; pub use self::config::*; pub use self::executor::*; use self::graph_utils::*; +pub use self::label::*; pub use self::migration::*; pub use self::schedule::*; pub use self::set::*; diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index c991bd57b8ba7..eebda6b876eb9 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -3,6 +3,7 @@ use crate::{ change_detection::MAX_CHANGE_AGE, component::ComponentId, query::Access, + schedule::{SystemLabel, SystemLabelId}, system::{ check_system_change_tick, AsSystemLabel, ExclusiveSystemParam, ExclusiveSystemParamItem, In, InputMarker, IntoSystem, System, SystemMeta, SystemTypeIdLabel, diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 92ebf0c50e7de..300f29eeca1e1 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -4,6 +4,7 @@ use crate::{ component::ComponentId, prelude::FromWorld, query::{Access, FilteredAccessSet}, + schedule::{SystemLabel, SystemLabelId}, system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem}, world::{World, WorldId}, }; From 0d04099d1b8bfa1f7b6316952b4c7e8e5250f4ec Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 10:35:34 -0500 Subject: [PATCH 009/247] Move migration methods to World and App --- crates/bevy_app/src/app.rs | 18 +++++++++++ crates/bevy_ecs/src/schedule/migration.rs | 38 ----------------------- crates/bevy_ecs/src/schedule/mod.rs | 2 -- crates/bevy_ecs/src/world/mod.rs | 12 +++++++ 4 files changed, 30 insertions(+), 40 deletions(-) delete mode 100644 crates/bevy_ecs/src/schedule/migration.rs diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 2a47aabb964c0..4881baaca4092 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -201,6 +201,24 @@ impl App { (runner)(app); } + /// Sets the [`Schedule`] that will be modified by default when you call `App::add_system` + /// and similar methods. + /// + /// **Note:** This will create the schedule if it does not already exist. + fn set_default_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self; + /// Applies the function to the [`Schedule`] associated with `label`. + /// + /// **Note:** This will create the schedule if it does not already exist. + fn edit_schedule( + &mut self, + label: impl ScheduleLabel, + f: impl FnMut(&mut Schedule), + ) -> &mut Self; + /// Adds [`State`] and [`NextState`] resources, [`OnEnter`] and [`OnExit`] schedules + /// for each state variant, and an instance of [`apply_state_transition::`] in + /// \ so that transitions happen before `Update`. + fn add_state(&mut self) -> &mut Self; + /// Adds a [`Stage`] with the given `label` to the last position of the app's /// [`Schedule`]. /// diff --git a/crates/bevy_ecs/src/schedule/migration.rs b/crates/bevy_ecs/src/schedule/migration.rs deleted file mode 100644 index 9c2dddbd0cf95..0000000000000 --- a/crates/bevy_ecs/src/schedule/migration.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::schedule::*; -use crate::world::World; - -/// Temporary "stageless" `App` methods. -pub trait AppExt { - /// Sets the [`Schedule`] that will be modified by default when you call `App::add_system` - /// and similar methods. - /// - /// **Note:** This will create the schedule if it does not already exist. - fn set_default_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self; - /// Applies the function to the [`Schedule`] associated with `label`. - /// - /// **Note:** This will create the schedule if it does not already exist. - fn edit_schedule( - &mut self, - label: impl ScheduleLabel, - f: impl FnMut(&mut Schedule), - ) -> &mut Self; - /// Adds [`State`] and [`NextState`] resources, [`OnEnter`] and [`OnExit`] schedules - /// for each state variant, and an instance of [`apply_state_transition::`] in - /// \ so that transitions happen before `Update`. - fn add_state(&mut self) -> &mut Self; -} - -/// Temporary "stageless" [`World`] methods. -pub trait WorldExt { - /// Runs the [`Schedule`] associated with `label`. - fn run_schedule(&mut self, label: impl ScheduleLabel); -} - -impl WorldExt for World { - fn run_schedule(&mut self, label: impl ScheduleLabel) { - if let Some(mut schedule) = self.resource_mut::().remove(&label) { - schedule.run(self); - self.resource_mut::().insert(label, schedule); - } - } -} diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index 5580be138dd5e..6f81da9999c66 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -3,7 +3,6 @@ mod config; mod executor; mod graph_utils; mod label; -mod migration; mod schedule; mod set; mod state; @@ -13,7 +12,6 @@ pub use self::config::*; pub use self::executor::*; use self::graph_utils::*; pub use self::label::*; -pub use self::migration::*; pub use self::schedule::*; pub use self::set::*; pub use self::state::*; diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 2c78661be2500..9d87a08277ff6 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -19,6 +19,7 @@ use crate::{ event::{Event, Events}, ptr::UnsafeCellDeref, query::{DebugCheckedUnwrap, QueryState, ReadOnlyWorldQuery, WorldQuery}, + schedule::{ScheduleLabel, Schedules}, storage::{Column, ComponentSparseSet, ResourceData, SparseSet, Storages, TableRow}, system::Resource, }; @@ -1972,6 +1973,17 @@ impl World { } } +// Schedule-related methods +impl World { + /// Runs the [`Schedule`](crate::schedule::Schedule) associated with the `label`. + pub fn run_schedule(&mut self, label: impl ScheduleLabel) { + if let Some(mut schedule) = self.resource_mut::().remove(&label) { + schedule.run(self); + self.resource_mut::().insert(label, schedule); + } + } +} + impl fmt::Debug for World { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("World") From 3eb7c4fb2dc9d79f9243e1cb8e29352a791dd86f Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 10:36:38 -0500 Subject: [PATCH 010/247] Remove stages benchmark --- benches/benches/bevy_ecs/scheduling/mod.rs | 2 - benches/benches/bevy_ecs/scheduling/stages.rs | 162 ------------------ 2 files changed, 164 deletions(-) delete mode 100644 benches/benches/bevy_ecs/scheduling/stages.rs diff --git a/benches/benches/bevy_ecs/scheduling/mod.rs b/benches/benches/bevy_ecs/scheduling/mod.rs index 21ccc56c04f63..5c083ab881dc4 100644 --- a/benches/benches/bevy_ecs/scheduling/mod.rs +++ b/benches/benches/bevy_ecs/scheduling/mod.rs @@ -2,11 +2,9 @@ use criterion::criterion_group; mod run_criteria; mod schedule; -mod stages; use run_criteria::*; use schedule::*; -use stages::*; criterion_group!( scheduling_benches, diff --git a/benches/benches/bevy_ecs/scheduling/stages.rs b/benches/benches/bevy_ecs/scheduling/stages.rs deleted file mode 100644 index fec9ba5eab305..0000000000000 --- a/benches/benches/bevy_ecs/scheduling/stages.rs +++ /dev/null @@ -1,162 +0,0 @@ -use bevy_ecs::{ - component::Component, - schedule::{Stage, SystemStage}, - system::Query, - world::World, -}; -use criterion::Criterion; - -fn run_stage(stage: &mut SystemStage, world: &mut World) { - stage.run(world); -} - -#[derive(Component)] -struct A(f32); -#[derive(Component)] -struct B(f32); -#[derive(Component)] -struct C(f32); -#[derive(Component)] -struct D(f32); -#[derive(Component)] -struct E(f32); - -const ENTITY_BUNCH: usize = 5000; - -pub fn empty_systems(criterion: &mut Criterion) { - let mut world = World::new(); - let mut group = criterion.benchmark_group("empty_systems"); - group.warm_up_time(std::time::Duration::from_millis(500)); - group.measurement_time(std::time::Duration::from_secs(3)); - fn empty() {} - for amount in 0..5 { - let mut stage = SystemStage::parallel(); - for _ in 0..amount { - stage.add_system(empty); - } - run_stage(&mut stage, &mut world); - group.bench_function(&format!("{:03}_systems", amount), |bencher| { - bencher.iter(|| { - run_stage(&mut stage, &mut world); - }); - }); - } - for amount in 1..21 { - let mut stage = SystemStage::parallel(); - for _ in 0..amount { - stage - .add_system(empty) - .add_system(empty) - .add_system(empty) - .add_system(empty) - .add_system(empty); - } - run_stage(&mut stage, &mut world); - group.bench_function(&format!("{:03}_systems", 5 * amount), |bencher| { - bencher.iter(|| { - run_stage(&mut stage, &mut world); - }); - }); - } - group.finish(); -} - -pub fn busy_systems(criterion: &mut Criterion) { - fn ab(mut q: Query<(&mut A, &mut B)>) { - q.for_each_mut(|(mut a, mut b)| { - std::mem::swap(&mut a.0, &mut b.0); - }); - } - fn cd(mut q: Query<(&mut C, &mut D)>) { - q.for_each_mut(|(mut c, mut d)| { - std::mem::swap(&mut c.0, &mut d.0); - }); - } - fn ce(mut q: Query<(&mut C, &mut E)>) { - q.for_each_mut(|(mut c, mut e)| { - std::mem::swap(&mut c.0, &mut e.0); - }); - } - let mut world = World::new(); - let mut group = criterion.benchmark_group("busy_systems"); - group.warm_up_time(std::time::Duration::from_millis(500)); - group.measurement_time(std::time::Duration::from_secs(3)); - for entity_bunches in 1..6 { - world.spawn_batch((0..4 * ENTITY_BUNCH).map(|_| (A(0.0), B(0.0)))); - world.spawn_batch((0..4 * ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0)))); - world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0), D(0.0)))); - world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0), E(0.0)))); - for system_amount in 0..5 { - let mut stage = SystemStage::parallel(); - stage.add_system(ab).add_system(cd).add_system(ce); - for _ in 0..system_amount { - stage.add_system(ab).add_system(cd).add_system(ce); - } - run_stage(&mut stage, &mut world); - group.bench_function( - &format!( - "{:02}x_entities_{:02}_systems", - entity_bunches, - 3 * system_amount + 3 - ), - |bencher| { - bencher.iter(|| { - run_stage(&mut stage, &mut world); - }); - }, - ); - } - } - group.finish(); -} - -pub fn contrived(criterion: &mut Criterion) { - fn s_0(mut q_0: Query<(&mut A, &mut B)>) { - q_0.for_each_mut(|(mut c_0, mut c_1)| { - std::mem::swap(&mut c_0.0, &mut c_1.0); - }); - } - fn s_1(mut q_0: Query<(&mut A, &mut C)>, mut q_1: Query<(&mut B, &mut D)>) { - q_0.for_each_mut(|(mut c_0, mut c_1)| { - std::mem::swap(&mut c_0.0, &mut c_1.0); - }); - q_1.for_each_mut(|(mut c_0, mut c_1)| { - std::mem::swap(&mut c_0.0, &mut c_1.0); - }); - } - fn s_2(mut q_0: Query<(&mut C, &mut D)>) { - q_0.for_each_mut(|(mut c_0, mut c_1)| { - std::mem::swap(&mut c_0.0, &mut c_1.0); - }); - } - let mut world = World::new(); - let mut group = criterion.benchmark_group("contrived"); - group.warm_up_time(std::time::Duration::from_millis(500)); - group.measurement_time(std::time::Duration::from_secs(3)); - for entity_bunches in 1..6 { - world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0), D(0.0)))); - world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0)))); - world.spawn_batch((0..ENTITY_BUNCH).map(|_| (C(0.0), D(0.0)))); - for system_amount in 0..5 { - let mut stage = SystemStage::parallel(); - stage.add_system(s_0).add_system(s_1).add_system(s_2); - for _ in 0..system_amount { - stage.add_system(s_0).add_system(s_1).add_system(s_2); - } - run_stage(&mut stage, &mut world); - group.bench_function( - &format!( - "{:02}x_entities_{:02}_systems", - entity_bunches, - 3 * system_amount + 3 - ), - |bencher| { - bencher.iter(|| { - run_stage(&mut stage, &mut world); - }); - }, - ); - } - } - group.finish(); -} From 32ebd9e813cde7fe11fe62d77b31b9ffe95a4a57 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 10:37:33 -0500 Subject: [PATCH 011/247] Dead import --- crates/bevy_ecs/src/schedule/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/schedule/state.rs b/crates/bevy_ecs/src/schedule/state.rs index 4425f76457c8f..eb1b9e11754e2 100644 --- a/crates/bevy_ecs/src/schedule/state.rs +++ b/crates/bevy_ecs/src/schedule/state.rs @@ -3,7 +3,7 @@ use std::hash::Hash; use std::mem; use crate as bevy_ecs; -use crate::schedule::{ScheduleLabel, SystemSet, WorldExt}; +use crate::schedule::{ScheduleLabel, SystemSet}; use crate::system::Resource; use crate::world::World; From 035ab6def867d217d4213bb3d51ddc742936f493 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 10:42:54 -0500 Subject: [PATCH 012/247] Remove stage-related methods from App --- crates/bevy_app/src/app.rs | 347 +------------------------------------ 1 file changed, 2 insertions(+), 345 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 4881baaca4092..9b10c5bc25219 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1,12 +1,9 @@ -use crate::{CoreStage, Plugin, PluginGroup, StartupSchedule, StartupStage}; +use crate::{CoreStage, Plugin, PluginGroup, StartupSchedule}; pub use bevy_derive::AppLabel; use bevy_ecs::{ event::{Event, Events}, prelude::FromWorld, - schedule::{ - IntoSystemDescriptor, Schedule, ShouldRun, Stage, StageLabel, State, StateData, SystemSet, - SystemStage, - }, + schedule::{IntoSystemDescriptor, Schedule, ShouldRun, State, StateData, SystemSet}, system::Resource, world::World, }; @@ -219,194 +216,6 @@ impl App { /// \ so that transitions happen before `Update`. fn add_state(&mut self) -> &mut Self; - /// Adds a [`Stage`] with the given `label` to the last position of the app's - /// [`Schedule`]. - /// - /// # Examples - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # let mut app = App::new(); - /// # - /// #[derive(StageLabel)] - /// struct MyStage; - /// app.add_stage(MyStage, SystemStage::parallel()); - /// ``` - pub fn add_stage(&mut self, label: impl StageLabel, stage: S) -> &mut Self { - self.schedule.add_stage(label, stage); - self - } - - /// Adds a [`Stage`] with the given `label` to the app's [`Schedule`], located - /// immediately after the stage labeled by `target`. - /// - /// # Examples - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # let mut app = App::new(); - /// # - /// #[derive(StageLabel)] - /// struct MyStage; - /// app.add_stage_after(CoreStage::Update, MyStage, SystemStage::parallel()); - /// ``` - pub fn add_stage_after( - &mut self, - target: impl StageLabel, - label: impl StageLabel, - stage: S, - ) -> &mut Self { - self.schedule.add_stage_after(target, label, stage); - self - } - - /// Adds a [`Stage`] with the given `label` to the app's [`Schedule`], located - /// immediately before the stage labeled by `target`. - /// - /// # Examples - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # let mut app = App::new(); - /// # - /// #[derive(StageLabel)] - /// struct MyStage; - /// app.add_stage_before(CoreStage::Update, MyStage, SystemStage::parallel()); - /// ``` - pub fn add_stage_before( - &mut self, - target: impl StageLabel, - label: impl StageLabel, - stage: S, - ) -> &mut Self { - self.schedule.add_stage_before(target, label, stage); - self - } - - /// Adds a [`Stage`] with the given `label` to the last position of the - /// [startup schedule](Self::add_default_stages). - /// - /// # Examples - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # let mut app = App::new(); - /// # - /// #[derive(StageLabel)] - /// struct MyStartupStage; - /// app.add_startup_stage(MyStartupStage, SystemStage::parallel()); - /// ``` - pub fn add_startup_stage(&mut self, label: impl StageLabel, stage: S) -> &mut Self { - self.schedule - .stage(StartupSchedule, |schedule: &mut Schedule| { - schedule.add_stage(label, stage) - }); - self - } - - /// Adds a [startup stage](Self::add_default_stages) with the given `label`, immediately - /// after the stage labeled by `target`. - /// - /// The `target` label must refer to a stage inside the startup schedule. - /// - /// # Examples - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # let mut app = App::new(); - /// # - /// #[derive(StageLabel)] - /// struct MyStartupStage; - /// app.add_startup_stage_after( - /// StartupStage::Startup, - /// MyStartupStage, - /// SystemStage::parallel() - /// ); - /// ``` - pub fn add_startup_stage_after( - &mut self, - target: impl StageLabel, - label: impl StageLabel, - stage: S, - ) -> &mut Self { - self.schedule - .stage(StartupSchedule, |schedule: &mut Schedule| { - schedule.add_stage_after(target, label, stage) - }); - self - } - - /// Adds a [startup stage](Self::add_default_stages) with the given `label`, immediately - /// before the stage labeled by `target`. - /// - /// The `target` label must refer to a stage inside the startup schedule. - /// - /// # Examples - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # let mut app = App::new(); - /// # - /// #[derive(StageLabel)] - /// struct MyStartupStage; - /// app.add_startup_stage_before( - /// StartupStage::Startup, - /// MyStartupStage, - /// SystemStage::parallel() - /// ); - /// ``` - pub fn add_startup_stage_before( - &mut self, - target: impl StageLabel, - label: impl StageLabel, - stage: S, - ) -> &mut Self { - self.schedule - .stage(StartupSchedule, |schedule: &mut Schedule| { - schedule.add_stage_before(target, label, stage) - }); - self - } - - /// Fetches the [`Stage`] of type `T` marked with `label` from the [`Schedule`], then - /// executes the provided `func` passing the fetched stage to it as an argument. - /// - /// The `func` argument should be a function or a closure that accepts a mutable reference - /// to a struct implementing `Stage` and returns the same type. That means that it should - /// also assume that the stage has already been fetched successfully. - /// - /// See [`stage`](Schedule::stage) for more details. - /// - /// # Examples - /// - /// Here the closure is used to add a system to the update stage: - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut app = App::new(); - /// # fn my_system() {} - /// # - /// app.stage(CoreStage::Update, |stage: &mut SystemStage| { - /// stage.add_system(my_system) - /// }); - /// ``` - pub fn stage &mut T>( - &mut self, - label: impl StageLabel, - func: F, - ) -> &mut Self { - self.schedule.stage(label, func); - self - } - /// Adds a system to the [update stage](Self::add_default_stages) of the app's [`Schedule`]. /// /// Refer to the [system module documentation](bevy_ecs::system) to see how a system @@ -451,69 +260,6 @@ impl App { self.add_system_set_to_stage(CoreStage::Update, system_set) } - /// Adds a system to the [`Stage`] identified by `stage_label`. - /// - /// # Examples - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut app = App::new(); - /// # fn my_system() {} - /// # - /// app.add_system_to_stage(CoreStage::PostUpdate, my_system); - /// ``` - pub fn add_system_to_stage( - &mut self, - stage_label: impl StageLabel, - system: impl IntoSystemDescriptor, - ) -> &mut Self { - use std::any::TypeId; - assert!( - stage_label.type_id() != TypeId::of::(), - "use `add_startup_system_to_stage` instead of `add_system_to_stage` to add a system to a StartupStage" - ); - self.schedule.add_system_to_stage(stage_label, system); - self - } - - /// Adds a [`SystemSet`] to the [`Stage`] identified by `stage_label`. - /// - /// # Examples - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut app = App::new(); - /// # fn system_a() {} - /// # fn system_b() {} - /// # fn system_c() {} - /// # - /// app.add_system_set_to_stage( - /// CoreStage::PostUpdate, - /// SystemSet::new() - /// .with_system(system_a) - /// .with_system(system_b) - /// .with_system(system_c), - /// ); - /// ``` - pub fn add_system_set_to_stage( - &mut self, - stage_label: impl StageLabel, - system_set: SystemSet, - ) -> &mut Self { - use std::any::TypeId; - assert!( - stage_label.type_id() != TypeId::of::(), - "use `add_startup_system_set_to_stage` instead of `add_system_set_to_stage` to add system sets to a StartupStage" - ); - self.schedule - .add_system_set_to_stage(stage_label, system_set); - self - } - /// Adds a system to the [startup stage](Self::add_default_stages) of the app's [`Schedule`]. /// /// * For adding a system that runs every frame, see [`add_system`](Self::add_system). @@ -563,95 +309,6 @@ impl App { self.add_startup_system_set_to_stage(StartupStage::Startup, system_set) } - /// Adds a system to the [startup schedule](Self::add_default_stages), in the stage - /// identified by `stage_label`. - /// - /// `stage_label` must refer to a stage inside the startup schedule. - /// - /// # Examples - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut app = App::new(); - /// # fn my_startup_system() {} - /// # - /// app.add_startup_system_to_stage(StartupStage::PreStartup, my_startup_system); - /// ``` - pub fn add_startup_system_to_stage( - &mut self, - stage_label: impl StageLabel, - system: impl IntoSystemDescriptor, - ) -> &mut Self { - self.schedule - .stage(StartupSchedule, |schedule: &mut Schedule| { - schedule.add_system_to_stage(stage_label, system) - }); - self - } - - /// Adds a [`SystemSet`] to the [startup schedule](Self::add_default_stages), in the stage - /// identified by `stage_label`. - /// - /// `stage_label` must refer to a stage inside the startup schedule. - /// - /// # Examples - /// - /// ``` - /// # use bevy_app::prelude::*; - /// # use bevy_ecs::prelude::*; - /// # - /// # let mut app = App::new(); - /// # fn startup_system_a() {} - /// # fn startup_system_b() {} - /// # fn startup_system_c() {} - /// # - /// app.add_startup_system_set_to_stage( - /// StartupStage::PreStartup, - /// SystemSet::new() - /// .with_system(startup_system_a) - /// .with_system(startup_system_b) - /// .with_system(startup_system_c), - /// ); - /// ``` - pub fn add_startup_system_set_to_stage( - &mut self, - stage_label: impl StageLabel, - system_set: SystemSet, - ) -> &mut Self { - self.schedule - .stage(StartupSchedule, |schedule: &mut Schedule| { - schedule.add_system_set_to_stage(stage_label, system_set) - }); - self - } - - /// Adds a new [`State`] with the given `initial` value. - /// This inserts a new `State` resource and adds a new "driver" to [`CoreStage::Update`]. - /// Each stage that uses `State` for system run criteria needs a driver. If you need to use - /// your state in a different stage, consider using [`Self::add_state_to_stage`] or manually - /// adding [`State::get_driver`] to additional stages you need it in. - pub fn add_state(&mut self, initial: T) -> &mut Self - where - T: StateData, - { - self.add_state_to_stage(CoreStage::Update, initial) - } - - /// Adds a new [`State`] with the given `initial` value. - /// This inserts a new `State` resource and adds a new "driver" to the given stage. - /// Each stage that uses `State` for system run criteria needs a driver. If you need to use - /// your state in more than one stage, consider manually adding [`State::get_driver`] to the - /// stages you need it in. - pub fn add_state_to_stage(&mut self, stage: impl StageLabel, initial: T) -> &mut Self - where - T: StateData, - { - self.insert_resource(State::new(initial)) - .add_system_set_to_stage(stage, State::::get_driver()) - } - /// Adds utility stages to the [`Schedule`], giving it a standardized structure. /// /// Adding those stages is necessary to make some core engine features work, like From 800ebe8b75a63b9f22f334c4a71bcffd1076e3ac Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 10:46:59 -0500 Subject: [PATCH 013/247] Rename App::add_default_stages --- crates/bevy_app/src/app.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 9b10c5bc25219..aa53e4688c7e3 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -119,7 +119,7 @@ impl Default for App { #[cfg(feature = "bevy_reflect")] app.init_resource::(); - app.add_default_stages().add_event::(); + app.add_default_sets().add_event::(); #[cfg(feature = "bevy_ci_testing")] { @@ -344,7 +344,7 @@ impl App { /// # /// let app = App::empty().add_default_stages(); /// ``` - pub fn add_default_stages(&mut self) -> &mut Self { + pub fn add_default_sets(&mut self) -> &mut Self { self.add_stage(CoreStage::First, SystemStage::parallel()) .add_stage( StartupSchedule, From 3e39929fe5455c059f28fa4a0446e2ae36c160a4 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 11:21:44 -0500 Subject: [PATCH 014/247] Migrate add_default_sets and add_default_schedules --- crates/bevy_animation/src/lib.rs | 4 +- crates/bevy_app/src/app.rs | 157 +++++++++++++----- crates/bevy_app/src/lib.rs | 36 ++-- crates/bevy_asset/src/lib.rs | 6 +- crates/bevy_audio/src/lib.rs | 7 +- crates/bevy_core/src/lib.rs | 2 +- .../src/log_diagnostics_plugin.rs | 4 +- crates/bevy_ecs/src/schedule/schedule.rs | 6 + crates/bevy_gilrs/src/lib.rs | 9 +- .../src/valid_parent_check_plugin.rs | 4 +- crates/bevy_input/src/lib.rs | 11 +- crates/bevy_pbr/src/lib.rs | 12 +- crates/bevy_render/src/camera/projection.rs | 6 +- crates/bevy_render/src/view/visibility/mod.rs | 14 +- crates/bevy_scene/src/lib.rs | 4 +- crates/bevy_text/src/lib.rs | 2 +- crates/bevy_time/src/lib.rs | 2 +- crates/bevy_transform/src/lib.rs | 8 +- crates/bevy_ui/src/lib.rs | 15 +- crates/bevy_window/src/lib.rs | 2 +- crates/bevy_winit/src/lib.rs | 4 +- examples/ecs/ecs_guide.rs | 8 +- examples/ecs/fixed_timestep.rs | 2 +- examples/ecs/iter_combinations.rs | 2 +- examples/ecs/removal_detection.rs | 4 +- examples/tools/scene_viewer/main.rs | 2 +- .../tools/scene_viewer/scene_viewer_plugin.rs | 2 +- 27 files changed, 202 insertions(+), 133 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 6e098f0e24e50..326eb24ccb60f 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -5,7 +5,7 @@ use std::ops::Deref; use std::time::Duration; -use bevy_app::{App, CoreStage, Plugin}; +use bevy_app::{App, CoreSet, Plugin}; use bevy_asset::{AddAsset, Assets, Handle}; use bevy_core::Name; use bevy_ecs::{ @@ -557,7 +557,7 @@ impl Plugin for AnimationPlugin { .register_asset_reflect::() .register_type::() .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, animation_player.before(TransformSystem::TransformPropagate), ); } diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index aa53e4688c7e3..98ac7c1cb3477 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1,9 +1,12 @@ -use crate::{CoreStage, Plugin, PluginGroup, StartupSchedule}; +use crate::{CoreSchedule, CoreSet, Plugin, PluginGroup, StartupSchedule, StartupSet}; pub use bevy_derive::AppLabel; use bevy_ecs::{ event::{Event, Events}, prelude::FromWorld, - schedule::{IntoSystemDescriptor, Schedule, ShouldRun, State, StateData, SystemSet}, + schedule::{ + apply_system_buffers, IntoSystemDescriptor, Schedule, Schedules, ShouldRun, State, + StateData, SystemSet, + }, system::Resource, world::World, }; @@ -65,8 +68,8 @@ pub struct App { /// Typically, it is not configured manually, but set by one of Bevy's built-in plugins. /// See `bevy::winit::WinitPlugin` and [`ScheduleRunnerPlugin`](crate::schedule_runner::ScheduleRunnerPlugin). pub runner: Box, - /// A container of [`Stage`]s set to be run in a linear order. - pub schedule: Schedule, + /// A collection of [`Schedule`] objects which are used to run systems + pub schedules: Schedules, sub_apps: HashMap, plugin_registry: Vec>, plugin_name_added: HashSet, @@ -119,7 +122,10 @@ impl Default for App { #[cfg(feature = "bevy_reflect")] app.init_resource::(); - app.add_default_sets().add_event::(); + app.add_default_schedules(); + app.add_default_sets(); + + app.add_event::(); #[cfg(feature = "bevy_ci_testing")] { @@ -133,6 +139,8 @@ impl Default for App { impl App { /// Creates a new [`App`] with some default structure to enable core engine features. /// This is the preferred constructor for most use cases. + /// + /// This calls [`App::add_default_schedules`] and [App::add_defaults_sets`]. pub fn new() -> App { App::default() } @@ -143,7 +151,7 @@ impl App { pub fn empty() -> App { Self { world: Default::default(), - schedule: Default::default(), + schedules: Default::default(), runner: Box::new(run_once), sub_apps: HashMap::default(), plugin_registry: Vec::default(), @@ -216,7 +224,7 @@ impl App { /// \ so that transitions happen before `Update`. fn add_state(&mut self) -> &mut Self; - /// Adds a system to the [update stage](Self::add_default_stages) of the app's [`Schedule`]. + /// Adds a system to the default system set and schedule of the app's [`Schedules`]. /// /// Refer to the [system module documentation](bevy_ecs::system) to see how a system /// can be defined. @@ -233,10 +241,10 @@ impl App { /// app.add_system(my_system); /// ``` pub fn add_system(&mut self, system: impl IntoSystemDescriptor) -> &mut Self { - self.add_system_to_stage(CoreStage::Update, system) + self.add_system_to_stage(CoreSet::Update, system) } - /// Adds a [`SystemSet`] to the [update stage](Self::add_default_stages). + /// Adds a system to the default system set and schedule of the app's [`Schedules`]. /// /// # Examples /// @@ -257,7 +265,7 @@ impl App { /// ); /// ``` pub fn add_system_set(&mut self, system_set: SystemSet) -> &mut Self { - self.add_system_set_to_stage(CoreStage::Update, system_set) + self.add_system_set_to_stage(CoreSet::Update, system_set) } /// Adds a system to the [startup stage](Self::add_default_stages) of the app's [`Schedule`]. @@ -309,55 +317,112 @@ impl App { self.add_startup_system_set_to_stage(StartupStage::Startup, system_set) } - /// Adds utility stages to the [`Schedule`], giving it a standardized structure. + /// Adds standardized schedules and labels to an [`App`]. /// - /// Adding those stages is necessary to make some core engine features work, like - /// adding systems without specifying a stage, or registering events. This is however - /// done by default by calling `App::default`, which is in turn called by + /// Adding these schedules is necessary to make some core engine features work. + /// This is however done by default by calling `App::default`, which is in turn called by /// [`App::new`]. /// - /// # The stages + /// The schedules are defined in the [`CoreSchedule`] enum. + /// + /// The [default schedule](App::set_default_schedule) becomes [`CoreSchedule::Main`]. + /// + /// You can also add standardized system sets to these schedules using [`App::add_default_sets`], + /// which must be called *after* this method as it relies on these schedules existing. /// - /// All the added stages, with the exception of the startup stage, run every time the - /// schedule is invoked. The stages are the following, in order of execution: + /// # Examples + /// + /// ``` + /// # use bevy_app::prelude::*; + /// # + /// let app = App::empty().add_default_schedules(); + /// ``` + pub fn add_default_schedules(&mut self) -> &mut Self { + app.schedules.insert(CoreSchedule::Startup, Schedule::new()); + app.schedules.insert(CoreSchedule::Main, Schedule::new()); + + app.set_default_schedule(CoreSchedule::Main); + + self + } + + /// Adds broad system sets to the default [`Schedule`], giving it a standardized linear structure. /// - /// - **First:** Runs at the very start of the schedule execution cycle, even before the - /// startup stage. - /// - **Startup:** This is actually a schedule containing sub-stages. Runs only once - /// when the app starts. - /// - **Pre-startup:** Intended for systems that need to run before other startup systems. - /// - **Startup:** The main startup stage. Startup systems are added here by default. - /// - **Post-startup:** Intended for systems that need to run after other startup systems. - /// - **Pre-update:** Often used by plugins to prepare their internal state before the - /// update stage begins. - /// - **Update:** Intended for user defined logic. Systems are added here by default. - /// - **Post-update:** Often used by plugins to finalize their internal state after the - /// world changes that happened during the update stage. - /// - **Last:** Runs right before the end of the schedule execution cycle. + /// See [`CoreSet`] and [`StartupSet`] for documentation on the system sets added. /// - /// The labels for those stages are defined in the [`CoreStage`] and [`StartupStage`] `enum`s. + /// The [default sets](bevy_ecs::schedule::Schedule::set_default_set) becomes [`CoreSet::Update`] and [`StartupSet::Startup`] respectively. + /// [`Command`](bevy_ecs::prelude::Commands) flush points, set with [`apply_system_buffers`] are added between each of these sets and at the end of each schedule. + /// + /// # Panics + /// + /// The [`CoreSchedule`] schedules must have been added to the [`App`] before this method is called. + /// You can do so using [`App::add_default_schedules`]. /// /// # Examples /// /// ``` /// # use bevy_app::prelude::*; /// # - /// let app = App::empty().add_default_stages(); + /// let app = App::empty().add_default_sets(); /// ``` pub fn add_default_sets(&mut self) -> &mut Self { - self.add_stage(CoreStage::First, SystemStage::parallel()) - .add_stage( - StartupSchedule, - Schedule::default() - .with_run_criteria(ShouldRun::once) - .with_stage(StartupStage::PreStartup, SystemStage::parallel()) - .with_stage(StartupStage::Startup, SystemStage::parallel()) - .with_stage(StartupStage::PostStartup, SystemStage::parallel()), - ) - .add_stage(CoreStage::PreUpdate, SystemStage::parallel()) - .add_stage(CoreStage::Update, SystemStage::parallel()) - .add_stage(CoreStage::PostUpdate, SystemStage::parallel()) - .add_stage(CoreStage::Last, SystemStage::parallel()) + let startup_schedule = self.schedules.get(CoreSchedule::Startup).unwrap(); + startup_schedule.add_set(StartupSet::PreStartup); + startup_schedule.add_set(StartupSet::Startup); + startup_schedule.add_set(StartupSet::PostStartup); + + startup_schedule.configure_set(StartupSet::PreStartup.before(StartupSet::Startup)); + startup_schedule.configure_set(StartupSet::PostStartup.after(StartupSet::Startup)); + + startup_schedule.add_system( + apply_system_buffers + .after(StartupSet::PreStartup) + .before(StartupSet::Startup), + ); + startup_schedule.add_system( + apply_system_buffers + .after(StartupSet::Startup) + .before(StartupSet::PostStartup), + ); + + startup_schedule.add_system(apply_system_buffers.after(StartupSet::PostStartup)); + + let main_schedule = self.schedules.get(CoreSchedule::Startup).unwrap(); + main_schedule.add_set(CoreSet::First); + main_schedule.add_set(CoreSet::PreUpdate); + main_schedule.add_set(CoreSet::Update); + main_schedule.add_set(CoreSet::PostUpdate); + main_schedule.add_set(CoreSet::Last); + + main_schedule.configure_set(CoreSet::First.before(CoreSet::PreUpdate)); + main_schedule.configure_set(CoreSet::PreUpdate.before(CoreSet::Update)); + main_schedule.configure_set(CoreSet::PostUpdate.after(CoreSet::Update)); + main_schedule.configure_set(CoreSet::Last.after(CoreSet::PostUpdate)); + + startup_schedule.add_system( + apply_system_buffers + .after(CoreSet::First) + .before(CoreSet::PreUpdate), + ); + startup_schedule.add_system( + apply_system_buffers + .after(CoreSet::PreUpdate) + .before(CoreSet::Update), + ); + + startup_schedule.add_system( + apply_system_buffers + .after(CoreSet::Update) + .before(CoreSet::PostUpdate), + ); + + startup_schedule.add_system( + apply_system_buffers + .after(CoreSet::PostUpdate) + .before(CoreSet::Last), + ); + + startup_schedule.add_system(apply_system_buffers.after(CoreSet::Last)); } /// Setup the application to manage events of type `T`. @@ -384,7 +449,7 @@ impl App { { if !self.world.contains_resource::>() { self.init_resource::>() - .add_system_to_stage(CoreStage::First, Events::::update_system); + .add_system_to_stage(CoreSet::First, Events::::update_system); } self } diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index d908c420ed0cc..bed8b6b4ae82b 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -23,26 +23,36 @@ pub mod prelude { pub use crate::AppTypeRegistry; #[doc(hidden)] pub use crate::{ - app::App, CoreStage, DynamicPlugin, Plugin, PluginGroup, StartupSchedule, StartupStage, + app::App, CoreSet, DynamicPlugin, Plugin, PluginGroup, StartupSchedule, StartupSet, }; } use bevy_ecs::schedule::StageLabel; -/// The names of the default [`App`] stages. +/// The names of the default [`App`] schedules. /// -/// The relative [`Stages`](bevy_ecs::schedule::Stage) are added by [`App::add_default_stages`]. +/// The corresponding [`Schedule`](bevy_ecs::schedule::Schedule) objects are added by [`App::add_default_schedules`]. +pub enum CoreSchedule { + /// The schedule that runs once when the app starts. + Startup, + /// The schedule that contains the app logic that is evaluated each tick of [`App::update()`]. + Main, +} + +/// The names of the default [`App`] system sets. +/// +/// The relative [`SystemSet`](bevy_ecs::schedule::SystemSet) are added by [`App::add_default_sets`]. #[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] -pub enum CoreStage { - /// The [`Stage`](bevy_ecs::schedule::Stage) that runs before all other app stages. +pub enum CoreSet { + /// Runs before all other app stages. First, - /// The [`Stage`](bevy_ecs::schedule::Stage) that runs before [`CoreStage::Update`]. + /// Runs before [`CoreStage::Update`]. PreUpdate, - /// The [`Stage`](bevy_ecs::schedule::Stage) responsible for doing most app logic. Systems should be registered here by default. + /// Responsible for doing most app logic. Systems should be registered here by default. Update, - /// The [`Stage`](bevy_ecs::schedule::Stage) that runs after [`CoreStage::Update`]. + /// Runs after [`CoreStage::Update`]. PostUpdate, - /// The [`Stage`](bevy_ecs::schedule::Stage) that runs after all other app stages. + /// Runs after all other app stages. Last, } @@ -56,11 +66,11 @@ pub struct StartupSchedule; /// The names of the default [`App`] startup stages. #[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] -pub enum StartupStage { - /// The [`Stage`](bevy_ecs::schedule::Stage) that runs once before [`StartupStage::Startup`]. +pub enum StartupSet { + /// Runs once before [`StartupSet::Startup`]. PreStartup, - /// The [`Stage`](bevy_ecs::schedule::Stage) that runs once when an [`App`] starts up. + /// Runs once when an [`App`] starts up. Startup, - /// The [`Stage`](bevy_ecs::schedule::Stage) that runs once after [`StartupStage::Startup`]. + /// Runs once after [`StartupSet::Startup`]. PostStartup, } diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index d0535fa6d2102..fef2737798b09 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -106,18 +106,18 @@ impl Plugin for AssetPlugin { } app.add_stage_before( - bevy_app::CoreStage::PreUpdate, + bevy_app::CoreSet::PreUpdate, AssetStage::LoadAssets, SystemStage::parallel(), ) .add_stage_after( - bevy_app::CoreStage::PostUpdate, + bevy_app::CoreSet::PostUpdate, AssetStage::AssetEvents, SystemStage::parallel(), ) .register_type::() .add_system_to_stage( - bevy_app::CoreStage::PreUpdate, + bevy_app::CoreSet::PreUpdate, asset_server::free_unused_assets_system, ); diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index f91c60a9b55ee..e72229df7079b 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -55,10 +55,7 @@ impl Plugin for AudioPlugin { .add_asset::() .add_asset::() .init_resource::>() - .add_system_to_stage( - CoreStage::PostUpdate, - play_queued_audio_system::, - ); + .add_system_to_stage(CoreSet::PostUpdate, play_queued_audio_system::); #[cfg(any(feature = "mp3", feature = "flac", feature = "wav", feature = "vorbis"))] app.init_asset_loader::(); @@ -73,6 +70,6 @@ impl AddAudioSource for App { self.add_asset::() .init_resource::>() .init_non_send_resource::>() - .add_system_to_stage(CoreStage::PostUpdate, play_queued_audio_system::) + .add_system_to_stage(CoreSet::PostUpdate, play_queued_audio_system::) } } diff --git a/crates/bevy_core/src/lib.rs b/crates/bevy_core/src/lib.rs index a3b91ea2d9fc7..223b62aabec44 100644 --- a/crates/bevy_core/src/lib.rs +++ b/crates/bevy_core/src/lib.rs @@ -108,7 +108,7 @@ impl Plugin for TaskPoolPlugin { #[cfg(not(target_arch = "wasm32"))] app.add_system_to_stage( - bevy_app::CoreStage::Last, + bevy_app::CoreSet::Last, tick_global_task_pools_on_main_thread.at_end(), ); } diff --git a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs index c0a06238396ed..e607ca29ad3dc 100644 --- a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs @@ -37,9 +37,9 @@ impl Plugin for LogDiagnosticsPlugin { }); if self.debug { - app.add_system_to_stage(CoreStage::PostUpdate, Self::log_diagnostics_debug_system); + app.add_system_to_stage(CoreSet::PostUpdate, Self::log_diagnostics_debug_system); } else { - app.add_system_to_stage(CoreStage::PostUpdate, Self::log_diagnostics_system); + app.add_system_to_stage(CoreSet::PostUpdate, Self::log_diagnostics_system); } } } diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index fc1b2f0ad71ba..0ef8ebd793c9f 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -123,6 +123,12 @@ impl Schedule { self } + /// Adds an empty system set to the schedule. + pub fn add_set(&mut self, set: impl SystemSet) { + let boxed_set = Box::new(set); + self.graph.add_set(boxed_set); + } + /// Configure a system set in this schedule. pub fn configure_set(&mut self, set: impl IntoSystemSetConfig) -> &mut Self { self.graph.configure_set(set); diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index 9002f10b476c0..a4b1d5535ffb8 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -1,7 +1,7 @@ mod converter; mod gilrs_system; -use bevy_app::{App, CoreStage, Plugin, StartupStage}; +use bevy_app::{App, CoreSet, Plugin, StartupSet}; use bevy_ecs::schedule::IntoSystemDescriptor; use bevy_input::InputSystem; use bevy_utils::tracing::error; @@ -20,12 +20,9 @@ impl Plugin for GilrsPlugin { { Ok(gilrs) => { app.insert_non_send_resource(gilrs) - .add_startup_system_to_stage( - StartupStage::PreStartup, - gilrs_event_startup_system, - ) + .add_startup_system_to_stage(StartupSet::PreStartup, gilrs_event_startup_system) .add_system_to_stage( - CoreStage::PreUpdate, + CoreSet::PreUpdate, gilrs_event_system.before(InputSystem), ); } diff --git a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs index 22a2bd088239f..93455dea142ae 100644 --- a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs +++ b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use bevy_app::{App, CoreStage, Plugin}; +use bevy_app::{App, CoreSet, Plugin}; use bevy_core::Name; use bevy_ecs::{prelude::*, schedule::ShouldRun}; use bevy_log::warn; @@ -81,7 +81,7 @@ impl Plugin for ValidParentCheckPlugin { fn build(&self, app: &mut App) { app.init_resource::>() .add_system_to_stage( - CoreStage::Last, + CoreSet::Last, check_hierarchy_component_has_valid_parent:: .with_run_criteria(on_hierarchy_reports_enabled::), ); diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index fccc2027f56b4..fe624cb899cba 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -56,17 +56,14 @@ impl Plugin for InputPlugin { .add_event::() .init_resource::>() .init_resource::>() - .add_system_to_stage( - CoreStage::PreUpdate, - keyboard_input_system.label(InputSystem), - ) + .add_system_to_stage(CoreSet::PreUpdate, keyboard_input_system.label(InputSystem)) // mouse .add_event::() .add_event::() .add_event::() .init_resource::>() .add_system_to_stage( - CoreStage::PreUpdate, + CoreSet::PreUpdate, mouse_button_input_system.label(InputSystem), ) // gamepad @@ -80,7 +77,7 @@ impl Plugin for InputPlugin { .init_resource::>() .init_resource::>() .add_system_set_to_stage( - CoreStage::PreUpdate, + CoreSet::PreUpdate, SystemSet::new() .with_system(gamepad_event_system) .with_system(gamepad_button_event_system.after(gamepad_event_system)) @@ -92,7 +89,7 @@ impl Plugin for InputPlugin { .add_event::() .init_resource::() .add_system_to_stage( - CoreStage::PreUpdate, + CoreSet::PreUpdate, touch_screen_input_system.label(InputSystem), ); diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 8366dbe673e1f..3d31ab83d82d8 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -142,7 +142,7 @@ impl Plugin for PbrPlugin { .init_resource::() .add_plugin(ExtractResourcePlugin::::default()) .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, // NOTE: Clusters need to have been added before update_clusters is run so // add as an exclusive system add_clusters @@ -150,7 +150,7 @@ impl Plugin for PbrPlugin { .label(SimulationLightSystems::AddClusters), ) .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, assign_lights_to_clusters .label(SimulationLightSystems::AssignLightsToClusters) .after(TransformSystem::TransformPropagate) @@ -159,7 +159,7 @@ impl Plugin for PbrPlugin { .after(ModifiesWindows), ) .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, update_directional_light_frusta .label(SimulationLightSystems::UpdateLightFrusta) // This must run after CheckVisibility because it relies on ComputedVisibility::is_visible() @@ -171,21 +171,21 @@ impl Plugin for PbrPlugin { .ambiguous_with(update_spot_light_frusta), ) .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, update_point_light_frusta .label(SimulationLightSystems::UpdateLightFrusta) .after(TransformSystem::TransformPropagate) .after(SimulationLightSystems::AssignLightsToClusters), ) .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, update_spot_light_frusta .label(SimulationLightSystems::UpdateLightFrusta) .after(TransformSystem::TransformPropagate) .after(SimulationLightSystems::AssignLightsToClusters), ) .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, check_light_mesh_visibility .label(SimulationLightSystems::CheckLightVisibility) .after(TransformSystem::TransformPropagate) diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index c3d6591e17b2d..89e1f2bbab35c 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use bevy_app::{App, CoreStage, Plugin, StartupStage}; +use bevy_app::{App, CoreSet, Plugin, StartupSet}; use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_math::Mat4; use bevy_reflect::{ @@ -29,7 +29,7 @@ impl Plugin for CameraPro fn build(&self, app: &mut App) { app.register_type::() .add_startup_system_to_stage( - StartupStage::PostStartup, + StartupSet::PostStartup, crate::camera::camera_system:: .label(CameraUpdateSystem) // We assume that each camera will only have one projection, @@ -38,7 +38,7 @@ impl Plugin for CameraPro .ambiguous_with(CameraUpdateSystem), ) .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, crate::camera::camera_system:: .label(CameraUpdateSystem) .after(ModifiesWindows) diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index f3381ac74c790..178612bc7fb44 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -2,7 +2,7 @@ mod render_layers; pub use render_layers::*; -use bevy_app::{CoreStage, Plugin}; +use bevy_app::{CoreSet, Plugin}; use bevy_asset::{Assets, Handle}; use bevy_ecs::prelude::*; use bevy_hierarchy::{Children, Parent}; @@ -211,11 +211,11 @@ impl Plugin for VisibilityPlugin { use VisibilitySystems::*; app.add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, calculate_bounds.label(CalculateBounds).before_commands(), ) .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, update_frusta:: .label(UpdateOrthographicFrusta) .after(camera_system::) @@ -227,7 +227,7 @@ impl Plugin for VisibilityPlugin { .ambiguous_with(update_frusta::), ) .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, update_frusta:: .label(UpdatePerspectiveFrusta) .after(camera_system::) @@ -238,18 +238,18 @@ impl Plugin for VisibilityPlugin { .ambiguous_with(update_frusta::), ) .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, update_frusta:: .label(UpdateProjectionFrusta) .after(camera_system::) .after(TransformSystem::TransformPropagate), ) .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, visibility_propagate_system.label(VisibilityPropagate), ) .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, check_visibility .label(CheckVisibility) .after(UpdateOrthographicFrusta) diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 184c2490d8718..13201c5f60335 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -36,9 +36,9 @@ impl Plugin for ScenePlugin { .add_asset::() .init_asset_loader::() .init_resource::() - .add_system_to_stage(CoreStage::PreUpdate, scene_spawner_system.at_end()) + .add_system_to_stage(CoreSet::PreUpdate, scene_spawner_system.at_end()) // Systems `*_bundle_spawner` must run before `scene_spawner_system` - .add_system_to_stage(CoreStage::PreUpdate, scene_spawner); + .add_system_to_stage(CoreSet::PreUpdate, scene_spawner); } } diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 84844ec5024ef..de971f0bf3fe0 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -81,7 +81,7 @@ impl Plugin for TextPlugin { .init_resource::() .insert_resource(TextPipeline::default()) .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, update_text2d_layout .after(ModifiesWindows) // Potential conflict: `Assets` diff --git a/crates/bevy_time/src/lib.rs b/crates/bevy_time/src/lib.rs index 86646e3f1c2ce..1e1b9c37e4524 100644 --- a/crates/bevy_time/src/lib.rs +++ b/crates/bevy_time/src/lib.rs @@ -41,7 +41,7 @@ impl Plugin for TimePlugin { .register_type::() // time system is added as an "exclusive system" to ensure it runs before other systems // in CoreStage::First - .add_system_to_stage(CoreStage::First, time_system.at_start().label(TimeSystem)); + .add_system_to_stage(CoreSet::First, time_system.at_start().label(TimeSystem)); } } diff --git a/crates/bevy_transform/src/lib.rs b/crates/bevy_transform/src/lib.rs index c68f01b2e0b5b..73455470dc8ad 100644 --- a/crates/bevy_transform/src/lib.rs +++ b/crates/bevy_transform/src/lib.rs @@ -100,19 +100,19 @@ impl Plugin for TransformPlugin { .add_plugin(ValidParentCheckPlugin::::default()) // add transform systems to startup so the first update is "correct" .add_startup_system_to_stage( - StartupStage::PostStartup, + StartupSet::PostStartup, systems::sync_simple_transforms.label(TransformSystem::TransformPropagate), ) .add_startup_system_to_stage( - StartupStage::PostStartup, + StartupSet::PostStartup, systems::propagate_transforms.label(TransformSystem::TransformPropagate), ) .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, systems::sync_simple_transforms.label(TransformSystem::TransformPropagate), ) .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, systems::propagate_transforms.label(TransformSystem::TransformPropagate), ); } diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 2ac32e95005ff..c2af86c3b6d14 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -105,12 +105,12 @@ impl Plugin for UiPlugin { .register_type::() .register_type::() .add_system_to_stage( - CoreStage::PreUpdate, + CoreSet::PreUpdate, ui_focus_system.label(UiSystem::Focus).after(InputSystem), ) // add these stages to front because these must run before transform update systems .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, widget::text_system .before(UiSystem::Flex) .after(ModifiesWindows) @@ -125,7 +125,7 @@ impl Plugin for UiPlugin { .ambiguous_with(bevy_text::update_text2d_layout), ) .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, widget::update_image_calculated_size_system .before(UiSystem::Flex) // Potential conflicts: `Assets` @@ -136,18 +136,15 @@ impl Plugin for UiPlugin { .ambiguous_with(widget::text_system), ) .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, flex_node_system .label(UiSystem::Flex) .before(TransformSystem::TransformPropagate) .after(ModifiesWindows), ) + .add_system_to_stage(CoreSet::PostUpdate, ui_stack_system.label(UiSystem::Stack)) .add_system_to_stage( - CoreStage::PostUpdate, - ui_stack_system.label(UiSystem::Stack), - ) - .add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, update_clipping_system.after(TransformSystem::TransformPropagate), ); diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index 1632c06042d66..b9acfd1e904e4 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -91,7 +91,7 @@ impl Plugin for WindowPlugin { if self.exit_on_all_closed { app.add_system_to_stage( - CoreStage::PostUpdate, + CoreSet::PostUpdate, exit_on_all_closed.after(ModifiesWindows), ); } diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 749d256882500..c747551929a61 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -8,7 +8,7 @@ use winit::window::CursorGrabMode; pub use winit_config::*; pub use winit_windows::*; -use bevy_app::{App, AppExit, CoreStage, Plugin}; +use bevy_app::{App, AppExit, CoreSet, Plugin}; use bevy_ecs::prelude::*; use bevy_ecs::{ event::{Events, ManualEventReader}, @@ -41,7 +41,7 @@ impl Plugin for WinitPlugin { app.init_non_send_resource::() .init_resource::() .set_runner(winit_runner) - .add_system_to_stage(CoreStage::PostUpdate, change_window.label(ModifiesWindows)); + .add_system_to_stage(CoreSet::PostUpdate, change_window.label(ModifiesWindows)); #[cfg(target_arch = "wasm32")] app.add_plugin(web_resize::CanvasParentResizePlugin); let event_loop = EventLoop::new(); diff --git a/examples/ecs/ecs_guide.rs b/examples/ecs/ecs_guide.rs index 0e64a9d56f1fe..073c43df4ace6 100644 --- a/examples/ecs/ecs_guide.rs +++ b/examples/ecs/ecs_guide.rs @@ -294,20 +294,20 @@ fn main() { // add_system(system) adds systems to the UPDATE stage by default // However we can manually specify the stage if we want to. The following is equivalent to // add_system(score_system) - .add_system_to_stage(CoreStage::Update, score_system) + .add_system_to_stage(CoreSet::Update, score_system) // There are other `CoreStages`, such as `Last` which runs at the very end of each run. - .add_system_to_stage(CoreStage::Last, print_at_end_round) + .add_system_to_stage(CoreSet::Last, print_at_end_round) // We can also create new stages. Here is what our games stage order will look like: // "before_round": new_player_system, new_round_system // "update": print_message_system, score_system // "after_round": score_check_system, game_over_system .add_stage_before( - CoreStage::Update, + CoreSet::Update, MyStage::BeforeRound, SystemStage::parallel(), ) .add_stage_after( - CoreStage::Update, + CoreSet::Update, MyStage::AfterRound, SystemStage::parallel(), ) diff --git a/examples/ecs/fixed_timestep.rs b/examples/ecs/fixed_timestep.rs index 77755dc42a58e..97cefdb7460fc 100644 --- a/examples/ecs/fixed_timestep.rs +++ b/examples/ecs/fixed_timestep.rs @@ -17,7 +17,7 @@ fn main() { .add_system(frame_update) // add a new stage that runs twice a second .add_stage_after( - CoreStage::Update, + CoreSet::Update, FixedUpdateStage, SystemStage::parallel() .with_run_criteria( diff --git a/examples/ecs/iter_combinations.rs b/examples/ecs/iter_combinations.rs index 7da35d5d27209..1830d567d3fe0 100644 --- a/examples/ecs/iter_combinations.rs +++ b/examples/ecs/iter_combinations.rs @@ -17,7 +17,7 @@ fn main() { }) .add_startup_system(generate_bodies) .add_stage_after( - CoreStage::Update, + CoreSet::Update, FixedUpdateStage, SystemStage::parallel() .with_run_criteria(FixedTimestep::step(DELTA_TIME)) diff --git a/examples/ecs/removal_detection.rs b/examples/ecs/removal_detection.rs index f931066a4a4ec..25b7805267441 100644 --- a/examples/ecs/removal_detection.rs +++ b/examples/ecs/removal_detection.rs @@ -16,8 +16,8 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_startup_system(setup) - .add_system_to_stage(CoreStage::Update, remove_component) - .add_system_to_stage(CoreStage::PostUpdate, react_on_removal) + .add_system_to_stage(CoreSet::Update, remove_component) + .add_system_to_stage(CoreSet::PostUpdate, react_on_removal) .run(); } diff --git a/examples/tools/scene_viewer/main.rs b/examples/tools/scene_viewer/main.rs index 323eaae0651f2..3a02d32ccb63c 100644 --- a/examples/tools/scene_viewer/main.rs +++ b/examples/tools/scene_viewer/main.rs @@ -41,7 +41,7 @@ fn main() { .add_plugin(CameraControllerPlugin) .add_plugin(SceneViewerPlugin) .add_startup_system(setup) - .add_system_to_stage(CoreStage::PreUpdate, setup_scene_after_load); + .add_system_to_stage(CoreSet::PreUpdate, setup_scene_after_load); app.run(); } diff --git a/examples/tools/scene_viewer/scene_viewer_plugin.rs b/examples/tools/scene_viewer/scene_viewer_plugin.rs index 7c23de67e88b0..a3a9dc279ad1b 100644 --- a/examples/tools/scene_viewer/scene_viewer_plugin.rs +++ b/examples/tools/scene_viewer/scene_viewer_plugin.rs @@ -60,7 +60,7 @@ pub struct SceneViewerPlugin; impl Plugin for SceneViewerPlugin { fn build(&self, app: &mut App) { app.init_resource::() - .add_system_to_stage(CoreStage::PreUpdate, scene_load_check) + .add_system_to_stage(CoreSet::PreUpdate, scene_load_check) .add_system(update_lights) .add_system(camera_tracker); From f6864d8ae2d7e3a5a71060a357f6ed52de94d5dd Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 11:25:08 -0500 Subject: [PATCH 015/247] Remember to set the default set --- crates/bevy_app/src/app.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 98ac7c1cb3477..06227428a0174 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -338,10 +338,11 @@ impl App { /// let app = App::empty().add_default_schedules(); /// ``` pub fn add_default_schedules(&mut self) -> &mut Self { - app.schedules.insert(CoreSchedule::Startup, Schedule::new()); - app.schedules.insert(CoreSchedule::Main, Schedule::new()); + self.schedules + .insert(CoreSchedule::Startup, Schedule::new()); + self.schedules.insert(CoreSchedule::Main, Schedule::new()); - app.set_default_schedule(CoreSchedule::Main); + self.set_default_schedule(CoreSchedule::Main); self } @@ -366,14 +367,17 @@ impl App { /// let app = App::empty().add_default_sets(); /// ``` pub fn add_default_sets(&mut self) -> &mut Self { + // Adding sets let startup_schedule = self.schedules.get(CoreSchedule::Startup).unwrap(); startup_schedule.add_set(StartupSet::PreStartup); startup_schedule.add_set(StartupSet::Startup); startup_schedule.add_set(StartupSet::PostStartup); + // Ordering startup_schedule.configure_set(StartupSet::PreStartup.before(StartupSet::Startup)); startup_schedule.configure_set(StartupSet::PostStartup.after(StartupSet::Startup)); + // Flush points startup_schedule.add_system( apply_system_buffers .after(StartupSet::PreStartup) @@ -387,6 +391,10 @@ impl App { startup_schedule.add_system(apply_system_buffers.after(StartupSet::PostStartup)); + // Default set + startup_schedule.set_default_set(StartupSet::Startup); + + // Adding sets let main_schedule = self.schedules.get(CoreSchedule::Startup).unwrap(); main_schedule.add_set(CoreSet::First); main_schedule.add_set(CoreSet::PreUpdate); @@ -394,11 +402,13 @@ impl App { main_schedule.add_set(CoreSet::PostUpdate); main_schedule.add_set(CoreSet::Last); + // Ordering main_schedule.configure_set(CoreSet::First.before(CoreSet::PreUpdate)); main_schedule.configure_set(CoreSet::PreUpdate.before(CoreSet::Update)); main_schedule.configure_set(CoreSet::PostUpdate.after(CoreSet::Update)); main_schedule.configure_set(CoreSet::Last.after(CoreSet::PostUpdate)); + // Flush points startup_schedule.add_system( apply_system_buffers .after(CoreSet::First) @@ -423,6 +433,9 @@ impl App { ); startup_schedule.add_system(apply_system_buffers.after(CoreSet::Last)); + + // Default set + startup_schedule.set_default_set(CoreSet::Update); } /// Setup the application to manage events of type `T`. From 73e4bbbe3cd612e67fe74d08eb058b6ce9002a75 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 11:31:27 -0500 Subject: [PATCH 016/247] Getting and setting the App's default schedule --- crates/bevy_app/src/app.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 06227428a0174..bb77431258d7a 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -4,8 +4,8 @@ use bevy_ecs::{ event::{Event, Events}, prelude::FromWorld, schedule::{ - apply_system_buffers, IntoSystemDescriptor, Schedule, Schedules, ShouldRun, State, - StateData, SystemSet, + apply_system_buffers, IntoSystemDescriptor, Schedule, ScheduleLabel, Schedules, ShouldRun, + State, StateData, SystemSet, }, system::Resource, world::World, @@ -68,8 +68,10 @@ pub struct App { /// Typically, it is not configured manually, but set by one of Bevy's built-in plugins. /// See `bevy::winit::WinitPlugin` and [`ScheduleRunnerPlugin`](crate::schedule_runner::ScheduleRunnerPlugin). pub runner: Box, - /// A collection of [`Schedule`] objects which are used to run systems + /// A collection of [`Schedule`] objects which are used to run systems. pub schedules: Schedules, + /// The [`Schedule`] that systems will be added to by default. + default_schedule: Option>, sub_apps: HashMap, plugin_registry: Vec>, plugin_name_added: HashSet, @@ -151,6 +153,7 @@ impl App { pub fn empty() -> App { Self { world: Default::default(), + default_schedule: None, schedules: Default::default(), runner: Box::new(run_once), sub_apps: HashMap::default(), @@ -210,7 +213,21 @@ impl App { /// and similar methods. /// /// **Note:** This will create the schedule if it does not already exist. - fn set_default_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self; + pub fn set_default_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self { + self.default_schedule = Some(Box::new(label)); + if self.schedules.get(&label).is_none() { + self.schedules.insert(label, Schedule::new()); + } + + self + } + + /// Gets the label of the [`Schedule`] that will be modified by default when you call `App::add_system` + /// and similar methods. + pub fn default_schedule(&mut self, label: impl ScheduleLabel) -> &Box { + &self.default_schedule + } + /// Applies the function to the [`Schedule`] associated with `label`. /// /// **Note:** This will create the schedule if it does not already exist. From 1a35fd29be8bfdfc583c5c52868e837e4cf95bc0 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 11:45:15 -0500 Subject: [PATCH 017/247] New add_system methods on App --- crates/bevy_app/src/app.rs | 77 ++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 15 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index bb77431258d7a..3bc8cca7787ea 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -4,8 +4,8 @@ use bevy_ecs::{ event::{Event, Events}, prelude::FromWorld, schedule::{ - apply_system_buffers, IntoSystemDescriptor, Schedule, ScheduleLabel, Schedules, ShouldRun, - State, StateData, SystemSet, + apply_system_buffers, IntoSystemConfig, IntoSystemConfigs, IntoSystemDescriptor, Schedule, + ScheduleLabel, Schedules, ShouldRun, State, StateData, SystemSet, }, system::Resource, world::World, @@ -257,8 +257,18 @@ impl App { /// # /// app.add_system(my_system); /// ``` - pub fn add_system(&mut self, system: impl IntoSystemDescriptor) -> &mut Self { - self.add_system_to_stage(CoreSet::Update, system) + pub fn add_system

(&mut self, system: impl IntoSystemConfig

) -> &mut Self { + if let Some(default_schedule_label) = self.default_schedule { + if let Some(default_schedule) = self.schedules.get_mut(&default_schedule_label) { + default_schedule.add_system(system); + } else { + panic!("Default schedule does not exist.") + } + } else { + panic!("No default schedule set for the `App`.") + } + + self } /// Adds a system to the default system set and schedule of the app's [`Schedules`]. @@ -281,11 +291,51 @@ impl App { /// .with_system(system_c), /// ); /// ``` - pub fn add_system_set(&mut self, system_set: SystemSet) -> &mut Self { - self.add_system_set_to_stage(CoreSet::Update, system_set) + pub fn add_systems

(&mut self, systems: impl IntoSystemConfigs

) -> &mut Self { + if let Some(default_schedule_label) = self.default_schedule { + if let Some(default_schedule) = self.schedules.get_mut(&default_schedule_label) { + default_schedule.add_systems(systems); + } else { + panic!("Default schedule does not exist.") + } + } else { + panic!("No default schedule set for the `App`.") + } + + self + } + + /// Adds a system to the provided [`Schedule`]. + pub fn add_system_to_schedule

( + &mut self, + system: impl IntoSystemConfig

, + schedule_label: impl ScheduleLabel, + ) -> &mut Self { + if let Some(schedule) = self.schedules.get_mut(&schedule_label) { + schedule.add_system(system); + } else { + panic!("Provided schedule {schedule_label:?} does not exist.") + } + + self + } + + /// Adds a collection of system to the provided [`Schedule`]. + pub fn add_systems_to_schedule

( + &mut self, + systems: impl IntoSystemConfigs

, + schedule_label: impl ScheduleLabel, + ) -> &mut Self { + if let Some(schedule) = self.schedules.get_mut(&schedule_label) { + schedule.add_systems(systems); + } else { + panic!("Provided schedule {schedule_label:?} does not exist.") + } + + self } - /// Adds a system to the [startup stage](Self::add_default_stages) of the app's [`Schedule`]. + /// Adds a system to [`CoreSchedule::Startup`]. /// /// * For adding a system that runs every frame, see [`add_system`](Self::add_system). /// * For adding a system to a specific stage, see [`add_system_to_stage`](Self::add_system_to_stage). @@ -303,14 +353,11 @@ impl App { /// App::new() /// .add_startup_system(my_startup_system); /// ``` - pub fn add_startup_system( - &mut self, - system: impl IntoSystemDescriptor, - ) -> &mut Self { - self.add_startup_system_to_stage(StartupStage::Startup, system) + pub fn add_startup_system

(&mut self, system: impl IntoSystemConfig

) -> &mut Self { + self.add_system_to_schedule(system, CoreSchedule::Startup) } - /// Adds a [`SystemSet`] to the [startup stage](Self::add_default_stages). + /// Adds a collection of systems to [`CoreSchedule::Startup`]. /// /// # Examples /// @@ -330,8 +377,8 @@ impl App { /// .with_system(startup_system_c), /// ); /// ``` - pub fn add_startup_system_set(&mut self, system_set: SystemSet) -> &mut Self { - self.add_startup_system_set_to_stage(StartupStage::Startup, system_set) + pub fn add_startup_systems

(&mut self, systems: impl IntoSystemConfigs

) -> &mut Self { + self.add_systems_to_schedule(systems, CoreSchedule::Startup) } /// Adds standardized schedules and labels to an [`App`]. From 48c989ed9e4e936c0921421bc5cdad1e8917887d Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 11:45:22 -0500 Subject: [PATCH 018/247] Renames --- examples/2d/rotation.rs | 2 +- examples/2d/texture_atlas.rs | 6 ++--- examples/ecs/generic_system.rs | 6 ++--- examples/ecs/state.rs | 10 ++++---- examples/ecs/system_sets.rs | 4 ++-- examples/games/alien_cake_addict.rs | 12 +++++----- examples/games/breakout.rs | 2 +- examples/games/game_menu.rs | 36 ++++++++++++++--------------- examples/stress_tests/bevymark.rs | 2 +- 9 files changed, 40 insertions(+), 40 deletions(-) diff --git a/examples/2d/rotation.rs b/examples/2d/rotation.rs index 61ab2b1a2e189..db3d3dce28beb 100644 --- a/examples/2d/rotation.rs +++ b/examples/2d/rotation.rs @@ -9,7 +9,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_startup_system(setup) - .add_system_set( + .add_systems( SystemSet::new() .with_run_criteria(FixedTimestep::step(TIME_STEP as f64)) .with_system(player_movement_system) diff --git a/examples/2d/texture_atlas.rs b/examples/2d/texture_atlas.rs index 14d9119ebe0e1..7a3ea33b43502 100644 --- a/examples/2d/texture_atlas.rs +++ b/examples/2d/texture_atlas.rs @@ -8,9 +8,9 @@ fn main() { .init_resource::() .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) // prevents blurry sprites .add_state(AppState::Setup) - .add_system_set(SystemSet::on_enter(AppState::Setup).with_system(load_textures)) - .add_system_set(SystemSet::on_update(AppState::Setup).with_system(check_textures)) - .add_system_set(SystemSet::on_enter(AppState::Finished).with_system(setup)) + .add_systems(SystemSet::on_enter(AppState::Setup).with_system(load_textures)) + .add_systems(SystemSet::on_update(AppState::Setup).with_system(check_textures)) + .add_systems(SystemSet::on_enter(AppState::Finished).with_system(setup)) .run(); } diff --git a/examples/ecs/generic_system.rs b/examples/ecs/generic_system.rs index d69bdf628ef05..1d7bf27b0cb91 100644 --- a/examples/ecs/generic_system.rs +++ b/examples/ecs/generic_system.rs @@ -35,15 +35,15 @@ fn main() { .add_state(AppState::MainMenu) .add_startup_system(setup_system) .add_system(print_text_system) - .add_system_set( + .add_systems( SystemSet::on_update(AppState::MainMenu).with_system(transition_to_in_game_system), ) // add the cleanup systems - .add_system_set( + .add_systems( // Pass in the types your system should operate on using the :: (turbofish) syntax SystemSet::on_exit(AppState::MainMenu).with_system(cleanup_system::), ) - .add_system_set( + .add_systems( SystemSet::on_exit(AppState::InGame).with_system(cleanup_system::), ) .run(); diff --git a/examples/ecs/state.rs b/examples/ecs/state.rs index 8980077a10de0..3543ada4800c6 100644 --- a/examples/ecs/state.rs +++ b/examples/ecs/state.rs @@ -8,11 +8,11 @@ fn main() { .add_plugins(DefaultPlugins) .add_state(AppState::Menu) .add_startup_system(setup) - .add_system_set(SystemSet::on_enter(AppState::Menu).with_system(setup_menu)) - .add_system_set(SystemSet::on_update(AppState::Menu).with_system(menu)) - .add_system_set(SystemSet::on_exit(AppState::Menu).with_system(cleanup_menu)) - .add_system_set(SystemSet::on_enter(AppState::InGame).with_system(setup_game)) - .add_system_set( + .add_systems(SystemSet::on_enter(AppState::Menu).with_system(setup_menu)) + .add_systems(SystemSet::on_update(AppState::Menu).with_system(menu)) + .add_systems(SystemSet::on_exit(AppState::Menu).with_system(cleanup_menu)) + .add_systems(SystemSet::on_enter(AppState::InGame).with_system(setup_game)) + .add_systems( SystemSet::on_update(AppState::InGame) .with_system(movement) .with_system(change_color), diff --git a/examples/ecs/system_sets.rs b/examples/ecs/system_sets.rs index 3461786768c09..33ce0e80ea596 100644 --- a/examples/ecs/system_sets.rs +++ b/examples/ecs/system_sets.rs @@ -51,7 +51,7 @@ fn main() { // use cases- typically state transitions. // Also note that a system set has a single run criterion at most, which means using `.with_run_criteria(...)` // after `SystemSet::on_update(...)` would override the state transition criterion. - .add_system_set( + .add_systems( SystemSet::new() // This label is added to all systems in this set. // The label can then be referred to elsewhere (other sets). @@ -63,7 +63,7 @@ fn main() { // Make movement run after update_velocity .with_system(movement.after(update_velocity)), ) - .add_system_set( + .add_systems( SystemSet::new() .label(PostPhysics) // This whole set runs after `Physics` (which in this case is a label for diff --git a/examples/games/alien_cake_addict.rs b/examples/games/alien_cake_addict.rs index bc4f04bd1ac6c..42dc8ae7669a1 100644 --- a/examples/games/alien_cake_addict.rs +++ b/examples/games/alien_cake_addict.rs @@ -24,8 +24,8 @@ fn main() { .add_plugins(DefaultPlugins) .add_state(GameState::Playing) .add_startup_system(setup_cameras) - .add_system_set(SystemSet::on_enter(GameState::Playing).with_system(setup)) - .add_system_set( + .add_systems(SystemSet::on_enter(GameState::Playing).with_system(setup)) + .add_systems( SystemSet::on_update(GameState::Playing) .with_system(move_player) .with_system(focus_camera) @@ -33,10 +33,10 @@ fn main() { .with_system(scoreboard_system) .with_system(spawn_bonus), ) - .add_system_set(SystemSet::on_exit(GameState::Playing).with_system(teardown)) - .add_system_set(SystemSet::on_enter(GameState::GameOver).with_system(display_score)) - .add_system_set(SystemSet::on_update(GameState::GameOver).with_system(gameover_keyboard)) - .add_system_set(SystemSet::on_exit(GameState::GameOver).with_system(teardown)) + .add_systems(SystemSet::on_exit(GameState::Playing).with_system(teardown)) + .add_systems(SystemSet::on_enter(GameState::GameOver).with_system(display_score)) + .add_systems(SystemSet::on_update(GameState::GameOver).with_system(gameover_keyboard)) + .add_systems(SystemSet::on_exit(GameState::GameOver).with_system(teardown)) .add_system(bevy::window::close_on_esc) .run(); } diff --git a/examples/games/breakout.rs b/examples/games/breakout.rs index 263a148b66cbd..2eb89d9a7c533 100644 --- a/examples/games/breakout.rs +++ b/examples/games/breakout.rs @@ -58,7 +58,7 @@ fn main() { .insert_resource(ClearColor(BACKGROUND_COLOR)) .add_startup_system(setup) .add_event::() - .add_system_set( + .add_systems( SystemSet::new() .with_run_criteria(FixedTimestep::step(TIME_STEP as f64)) .with_system(check_for_collisions) diff --git a/examples/games/game_menu.rs b/examples/games/game_menu.rs index 58daa1f307cd1..71d79f24fad91 100644 --- a/examples/games/game_menu.rs +++ b/examples/games/game_menu.rs @@ -59,11 +59,11 @@ mod splash { // As this plugin is managing the splash screen, it will focus on the state `GameState::Splash` app // When entering the state, spawn everything needed for this screen - .add_system_set(SystemSet::on_enter(GameState::Splash).with_system(splash_setup)) + .add_systems(SystemSet::on_enter(GameState::Splash).with_system(splash_setup)) // While in this state, run the `countdown` system - .add_system_set(SystemSet::on_update(GameState::Splash).with_system(countdown)) + .add_systems(SystemSet::on_update(GameState::Splash).with_system(countdown)) // When exiting the state, despawn everything that was spawned for this screen - .add_system_set( + .add_systems( SystemSet::on_exit(GameState::Splash) .with_system(despawn_screen::), ); @@ -132,9 +132,9 @@ mod game { impl Plugin for GamePlugin { fn build(&self, app: &mut App) { - app.add_system_set(SystemSet::on_enter(GameState::Game).with_system(game_setup)) - .add_system_set(SystemSet::on_update(GameState::Game).with_system(game)) - .add_system_set( + app.add_systems(SystemSet::on_enter(GameState::Game).with_system(game_setup)) + .add_systems(SystemSet::on_update(GameState::Game).with_system(game)) + .add_systems( SystemSet::on_exit(GameState::Game).with_system(despawn_screen::), ); } @@ -269,49 +269,49 @@ mod menu { // entering the `GameState::Menu` state. // Current screen in the menu is handled by an independent state from `GameState` .add_state(MenuState::Disabled) - .add_system_set(SystemSet::on_enter(GameState::Menu).with_system(menu_setup)) + .add_systems(SystemSet::on_enter(GameState::Menu).with_system(menu_setup)) // Systems to handle the main menu screen - .add_system_set(SystemSet::on_enter(MenuState::Main).with_system(main_menu_setup)) - .add_system_set( + .add_systems(SystemSet::on_enter(MenuState::Main).with_system(main_menu_setup)) + .add_systems( SystemSet::on_exit(MenuState::Main) .with_system(despawn_screen::), ) // Systems to handle the settings menu screen - .add_system_set( + .add_systems( SystemSet::on_enter(MenuState::Settings).with_system(settings_menu_setup), ) - .add_system_set( + .add_systems( SystemSet::on_exit(MenuState::Settings) .with_system(despawn_screen::), ) // Systems to handle the display settings screen - .add_system_set( + .add_systems( SystemSet::on_enter(MenuState::SettingsDisplay) .with_system(display_settings_menu_setup), ) - .add_system_set( + .add_systems( SystemSet::on_update(MenuState::SettingsDisplay) .with_system(setting_button::), ) - .add_system_set( + .add_systems( SystemSet::on_exit(MenuState::SettingsDisplay) .with_system(despawn_screen::), ) // Systems to handle the sound settings screen - .add_system_set( + .add_systems( SystemSet::on_enter(MenuState::SettingsSound) .with_system(sound_settings_menu_setup), ) - .add_system_set( + .add_systems( SystemSet::on_update(MenuState::SettingsSound) .with_system(setting_button::), ) - .add_system_set( + .add_systems( SystemSet::on_exit(MenuState::SettingsSound) .with_system(despawn_screen::), ) // Common systems to all screens that handles buttons behaviour - .add_system_set( + .add_systems( SystemSet::on_update(GameState::Menu) .with_system(menu_action) .with_system(button_system), diff --git a/examples/stress_tests/bevymark.rs b/examples/stress_tests/bevymark.rs index 42dc723540ceb..a268562bbb7be 100644 --- a/examples/stress_tests/bevymark.rs +++ b/examples/stress_tests/bevymark.rs @@ -51,7 +51,7 @@ fn main() { .add_system(movement_system) .add_system(collision_system) .add_system(counter_system) - .add_system_set( + .add_systems( SystemSet::new() .with_run_criteria(FixedTimestep::step(0.2)) .with_system(scheduled_spawner), From 585d704dc2df982b2d878984a4634ab5787e20b7 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 11:46:58 -0500 Subject: [PATCH 019/247] Remove StartupSchedule label type --- crates/bevy_app/src/app.rs | 2 +- crates/bevy_app/src/lib.rs | 12 +----------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 3bc8cca7787ea..c3791562adf6b 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1,4 +1,4 @@ -use crate::{CoreSchedule, CoreSet, Plugin, PluginGroup, StartupSchedule, StartupSet}; +use crate::{CoreSchedule, CoreSet, Plugin, PluginGroup, StartupSet}; pub use bevy_derive::AppLabel; use bevy_ecs::{ event::{Event, Events}, diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index bed8b6b4ae82b..3ed2e5538297b 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -22,9 +22,7 @@ pub mod prelude { #[doc(hidden)] pub use crate::AppTypeRegistry; #[doc(hidden)] - pub use crate::{ - app::App, CoreSet, DynamicPlugin, Plugin, PluginGroup, StartupSchedule, StartupSet, - }; + pub use crate::{app::App, CoreSet, DynamicPlugin, Plugin, PluginGroup, StartupSet}; } use bevy_ecs::schedule::StageLabel; @@ -56,14 +54,6 @@ pub enum CoreSet { Last, } -/// The label for the startup [`Schedule`](bevy_ecs::schedule::Schedule), -/// which runs once at the beginning of the [`App`]. -/// -/// When targeting a [`Stage`](bevy_ecs::schedule::Stage) inside this [`Schedule`](bevy_ecs::schedule::Schedule), -/// you need to use [`StartupStage`] instead. -#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] -pub struct StartupSchedule; - /// The names of the default [`App`] startup stages. #[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] pub enum StartupSet { From c787e336861ed741cf8e92f5b622281ab79322c5 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 11:47:36 -0500 Subject: [PATCH 020/247] Remove dead imports in App --- crates/bevy_app/src/app.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index c3791562adf6b..0cfc9c93c6ad1 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -4,8 +4,8 @@ use bevy_ecs::{ event::{Event, Events}, prelude::FromWorld, schedule::{ - apply_system_buffers, IntoSystemConfig, IntoSystemConfigs, IntoSystemDescriptor, Schedule, - ScheduleLabel, Schedules, ShouldRun, State, StateData, SystemSet, + apply_system_buffers, IntoSystemConfig, IntoSystemConfigs, Schedule, ScheduleLabel, + Schedules, }, system::Resource, world::World, From 7c32fcd0defcc6cdad55aaed608adf769e991347 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 11:49:49 -0500 Subject: [PATCH 021/247] Write App::edit_schedule --- crates/bevy_app/src/app.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 0cfc9c93c6ad1..68f484cee95eb 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -235,7 +235,18 @@ impl App { &mut self, label: impl ScheduleLabel, f: impl FnMut(&mut Schedule), - ) -> &mut Self; + ) -> &mut Self { + if self.schedules.get(&label).is_none() { + self.schedules.insert(label, Schedule::new()); + } + + let schedule = self.schedules.get_mut(&label).unwrap(); + // Call the function f, passing in the schedule retrieved + f(schedule); + + self + } + /// Adds [`State`] and [`NextState`] resources, [`OnEnter`] and [`OnExit`] schedules /// for each state variant, and an instance of [`apply_state_transition::`] in /// \ so that transitions happen before `Update`. From b10d50991b9694563fc3b6d464ba819012068e6f Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 11:54:22 -0500 Subject: [PATCH 022/247] Fix SubApp::run --- crates/bevy_app/src/app.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 68f484cee95eb..e9a09577b68b9 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -71,7 +71,7 @@ pub struct App { /// A collection of [`Schedule`] objects which are used to run systems. pub schedules: Schedules, /// The [`Schedule`] that systems will be added to by default. - default_schedule: Option>, + default_schedule_label: Option>, sub_apps: HashMap, plugin_registry: Vec>, plugin_name_added: HashSet, @@ -98,7 +98,8 @@ struct SubApp { impl SubApp { /// Runs the `SubApp`'s schedule. pub fn run(&mut self) { - self.app.schedule.run(&mut self.app.world); + let default_schedule = self.app.schedules.get_mut(self.app.default_schedule_label); + self.app.world.run_schedule(default_schedule); self.app.world.clear_trackers(); } @@ -153,7 +154,7 @@ impl App { pub fn empty() -> App { Self { world: Default::default(), - default_schedule: None, + default_schedule_label: None, schedules: Default::default(), runner: Box::new(run_once), sub_apps: HashMap::default(), @@ -214,7 +215,7 @@ impl App { /// /// **Note:** This will create the schedule if it does not already exist. pub fn set_default_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self { - self.default_schedule = Some(Box::new(label)); + self.default_schedule_label = Some(Box::new(label)); if self.schedules.get(&label).is_none() { self.schedules.insert(label, Schedule::new()); } @@ -225,7 +226,7 @@ impl App { /// Gets the label of the [`Schedule`] that will be modified by default when you call `App::add_system` /// and similar methods. pub fn default_schedule(&mut self, label: impl ScheduleLabel) -> &Box { - &self.default_schedule + &self.default_schedule_label } /// Applies the function to the [`Schedule`] associated with `label`. @@ -269,7 +270,7 @@ impl App { /// app.add_system(my_system); /// ``` pub fn add_system

(&mut self, system: impl IntoSystemConfig

) -> &mut Self { - if let Some(default_schedule_label) = self.default_schedule { + if let Some(default_schedule_label) = self.default_schedule_label { if let Some(default_schedule) = self.schedules.get_mut(&default_schedule_label) { default_schedule.add_system(system); } else { @@ -303,7 +304,7 @@ impl App { /// ); /// ``` pub fn add_systems

(&mut self, systems: impl IntoSystemConfigs

) -> &mut Self { - if let Some(default_schedule_label) = self.default_schedule { + if let Some(default_schedule_label) = self.default_schedule_label { if let Some(default_schedule) = self.schedules.get_mut(&default_schedule_label) { default_schedule.add_systems(systems); } else { From 5aa13c83f3458a225bb57de62bdeeac7d15db547 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 12:30:38 -0500 Subject: [PATCH 023/247] Migrate usages of add_system_to_stage --- crates/bevy_animation/src/lib.rs | 7 +- crates/bevy_app/src/app.rs | 6 +- crates/bevy_asset/src/assets.rs | 4 +- crates/bevy_asset/src/lib.rs | 7 +- crates/bevy_audio/src/lib.rs | 4 +- crates/bevy_core/src/lib.rs | 7 +- crates/bevy_core_pipeline/src/bloom/mod.rs | 4 +- crates/bevy_core_pipeline/src/core_2d/mod.rs | 10 +-- crates/bevy_core_pipeline/src/core_3d/mod.rs | 10 +-- crates/bevy_core_pipeline/src/fxaa/mod.rs | 2 +- .../bevy_core_pipeline/src/tonemapping/mod.rs | 2 +- .../bevy_core_pipeline/src/upscaling/mod.rs | 2 +- .../src/log_diagnostics_plugin.rs | 4 +- crates/bevy_ecs/src/system/system_param.rs | 4 +- crates/bevy_ecs/src/system/system_piping.rs | 21 ++---- crates/bevy_gilrs/src/lib.rs | 9 +-- .../src/valid_parent_check_plugin.rs | 11 ++-- crates/bevy_input/src/lib.rs | 26 +++++--- crates/bevy_pbr/src/lib.rs | 64 ++++++++++--------- crates/bevy_pbr/src/material.rs | 11 ++-- crates/bevy_pbr/src/render/mesh.rs | 10 +-- crates/bevy_pbr/src/wireframe.rs | 4 +- crates/bevy_render/src/camera/mod.rs | 2 +- crates/bevy_render/src/camera/projection.rs | 8 +-- crates/bevy_render/src/extract_component.rs | 7 +- crates/bevy_render/src/extract_resource.rs | 2 +- crates/bevy_render/src/globals.rs | 6 +- crates/bevy_render/src/render_asset.rs | 4 +- crates/bevy_render/src/texture/mod.rs | 2 +- crates/bevy_render/src/view/mod.rs | 9 +-- crates/bevy_render/src/view/visibility/mod.rs | 31 +++++---- crates/bevy_render/src/view/window.rs | 9 +-- crates/bevy_scene/src/lib.rs | 4 +- crates/bevy_sprite/src/lib.rs | 11 ++-- crates/bevy_sprite/src/mesh2d/material.rs | 11 ++-- crates/bevy_sprite/src/mesh2d/mesh.rs | 6 +- crates/bevy_text/src/lib.rs | 11 ++-- crates/bevy_time/src/lib.rs | 7 +- crates/bevy_transform/src/lib.rs | 28 ++++---- crates/bevy_ui/src/lib.rs | 33 ++++++---- crates/bevy_ui/src/render/mod.rs | 30 ++++----- crates/bevy_window/src/lib.rs | 7 +- crates/bevy_winit/src/lib.rs | 6 +- 43 files changed, 244 insertions(+), 219 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 326eb24ccb60f..26ae513fc7380 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -556,9 +556,10 @@ impl Plugin for AnimationPlugin { app.add_asset::() .register_asset_reflect::() .register_type::() - .add_system_to_stage( - CoreSet::PostUpdate, - animation_player.before(TransformSystem::TransformPropagate), + .add_system( + animation_player + .label(CoreSet::PostUpdate) + .before(TransformSystem::TransformPropagate), ); } } diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index e9a09577b68b9..79f37fc5049b2 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -349,8 +349,8 @@ impl App { /// Adds a system to [`CoreSchedule::Startup`]. /// - /// * For adding a system that runs every frame, see [`add_system`](Self::add_system). - /// * For adding a system to a specific stage, see [`add_system_to_stage`](Self::add_system_to_stage). + /// These systems will run exactly once, at the start of the [`App`]'s lifecycle. + /// To add a system that runs every frame, see [`add_system`](Self::add_system). /// /// # Examples /// @@ -538,7 +538,7 @@ impl App { { if !self.world.contains_resource::>() { self.init_resource::>() - .add_system_to_stage(CoreSet::First, Events::::update_system); + .add_system(Events::::update_system.label(CoreSet::First)); } self } diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 1a3a300553990..aa9092794cc89 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -335,8 +335,8 @@ impl AddAsset for App { }; self.insert_resource(assets) - .add_system_to_stage(AssetStage::AssetEvents, Assets::::asset_event_system) - .add_system_to_stage(AssetStage::LoadAssets, update_asset_storage_system::) + .add_system(Assets::::asset_event_system.label(AssetStage::AssetEvents)) + .add_system(update_asset_storage_system::.label(AssetStage::LoadAssets)) .register_type::>() .add_event::>() } diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index fef2737798b09..b322d59d74ebb 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -116,15 +116,12 @@ impl Plugin for AssetPlugin { SystemStage::parallel(), ) .register_type::() - .add_system_to_stage( - bevy_app::CoreSet::PreUpdate, - asset_server::free_unused_assets_system, - ); + .add_system(asset_server::free_unused_assets_system.label(CoreSet::PreUpdate)); #[cfg(all( feature = "filesystem_watcher", all(not(target_arch = "wasm32"), not(target_os = "android")) ))] - app.add_system_to_stage(AssetStage::LoadAssets, io::filesystem_watcher_system); + app.add_system(io::filesystem_watcher_system.label(AssetStage::LoadAssets)); } } diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index e72229df7079b..a7cd639369f79 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -55,7 +55,7 @@ impl Plugin for AudioPlugin { .add_asset::() .add_asset::() .init_resource::>() - .add_system_to_stage(CoreSet::PostUpdate, play_queued_audio_system::); + .add_system(play_queued_audio_system::.label(CoreSet::PostUpdate)); #[cfg(any(feature = "mp3", feature = "flac", feature = "wav", feature = "vorbis"))] app.init_asset_loader::(); @@ -70,6 +70,6 @@ impl AddAudioSource for App { self.add_asset::() .init_resource::>() .init_non_send_resource::>() - .add_system_to_stage(CoreSet::PostUpdate, play_queued_audio_system::) + .add_system(play_queued_audio_system::.label(CoreSet::PostUpdate)) } } diff --git a/crates/bevy_core/src/lib.rs b/crates/bevy_core/src/lib.rs index 223b62aabec44..62a3a4054449b 100644 --- a/crates/bevy_core/src/lib.rs +++ b/crates/bevy_core/src/lib.rs @@ -107,9 +107,10 @@ impl Plugin for TaskPoolPlugin { self.task_pool_options.create_default_pools(); #[cfg(not(target_arch = "wasm32"))] - app.add_system_to_stage( - bevy_app::CoreSet::Last, - tick_global_task_pools_on_main_thread.at_end(), + app.add_system( + tick_global_task_pools_on_main_thread + .at_end() + .label(bevy_app::CoreSet::Last), ); } } diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index ad438b2a25dcf..94ff986d7c0ce 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -48,8 +48,8 @@ impl Plugin for BloomPlugin { render_app .init_resource::() - .add_system_to_stage(RenderStage::Prepare, prepare_bloom_textures) - .add_system_to_stage(RenderStage::Queue, queue_bloom_bind_groups); + .add_system(prepare_bloom_textures.label(RenderStage::Prepare)) + .add_system(queue_bloom_bind_groups.label(RenderStage::Queue)); { let bloom_node = BloomNode::new(&mut render_app.world); diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index f391a49496a04..4e0ffe146f295 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -51,11 +51,11 @@ impl Plugin for Core2dPlugin { render_app .init_resource::>() - .add_system_to_stage(RenderStage::Extract, extract_core_2d_camera_phases) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) - .add_system_to_stage( - RenderStage::PhaseSort, - batch_phase_system::.after(sort_phase_system::), + .add_system(extract_core_2d_camera_phases.label(RenderStage::Extract)) + .add_system(sort_phase_system::.label(RenderStage::PhaseSort)) + .add_system( + batch_phase_system:: + .after(sort_phase_system::.label(RenderStage::PhaseSort)), ); let pass_node_2d = MainPass2dNode::new(&mut render_app.world); diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 34a430a345398..cb19a45d92c03 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -62,11 +62,11 @@ impl Plugin for Core3dPlugin { .init_resource::>() .init_resource::>() .init_resource::>() - .add_system_to_stage(RenderStage::Extract, extract_core_3d_camera_phases) - .add_system_to_stage(RenderStage::Prepare, prepare_core_3d_depth_textures) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::); + .add_system(extract_core_3d_camera_phases.label(RenderStage::Extract)) + .add_system(prepare_core_3d_depth_textures.label(RenderStage::Prepare)) + .add_system(sort_phase_system::.label(RenderStage::PhaseSort)) + .add_system(sort_phase_system::.label(RenderStage::PhaseSort)) + .add_system(sort_phase_system::.label(RenderStage::PhaseSort)); let pass_node_3d = MainPass3dNode::new(&mut render_app.world); let tonemapping = TonemappingNode::new(&mut render_app.world); diff --git a/crates/bevy_core_pipeline/src/fxaa/mod.rs b/crates/bevy_core_pipeline/src/fxaa/mod.rs index 4b2a7a2ea9401..4eaa73f09ded5 100644 --- a/crates/bevy_core_pipeline/src/fxaa/mod.rs +++ b/crates/bevy_core_pipeline/src/fxaa/mod.rs @@ -95,7 +95,7 @@ impl Plugin for FxaaPlugin { render_app .init_resource::() .init_resource::>() - .add_system_to_stage(RenderStage::Prepare, prepare_fxaa_pipelines); + .add_system(prepare_fxaa_pipelines.label(RenderStage::Prepare)); { let fxaa_node = FxaaNode::new(&mut render_app.world); diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index f7233fd575102..d49644ab8e3fd 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -45,7 +45,7 @@ impl Plugin for TonemappingPlugin { render_app .init_resource::() .init_resource::>() - .add_system_to_stage(RenderStage::Queue, queue_view_tonemapping_pipelines); + .add_system(queue_view_tonemapping_pipelines.label(RenderStage::Queue)); } } } diff --git a/crates/bevy_core_pipeline/src/upscaling/mod.rs b/crates/bevy_core_pipeline/src/upscaling/mod.rs index a25e10632cd90..8f936a91d870e 100644 --- a/crates/bevy_core_pipeline/src/upscaling/mod.rs +++ b/crates/bevy_core_pipeline/src/upscaling/mod.rs @@ -29,7 +29,7 @@ impl Plugin for UpscalingPlugin { render_app .init_resource::() .init_resource::>() - .add_system_to_stage(RenderStage::Queue, queue_view_upscaling_pipelines); + .add_system(queue_view_upscaling_pipelines.label(RenderStage::Queue)); } } } diff --git a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs index e607ca29ad3dc..a23ec199d6a35 100644 --- a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs @@ -37,9 +37,9 @@ impl Plugin for LogDiagnosticsPlugin { }); if self.debug { - app.add_system_to_stage(CoreSet::PostUpdate, Self::log_diagnostics_debug_system); + app.add_system(Self::log_diagnostics_debug_system.label(CoreSet::PostUpdate)); } else { - app.add_system_to_stage(CoreSet::PostUpdate, Self::log_diagnostics_system); + app.add_system(Self::log_diagnostics_system.label(CoreSet::PostUpdate)); } } } diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 9cb7fb3dfc8b2..be54ce7696fe1 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -397,8 +397,8 @@ impl_param_set!(); /// resource.value = 0; /// assert_eq!(resource.value, 0); /// } -/// # schedule.add_system_to_stage("update", read_resource_system.label("first")); -/// # schedule.add_system_to_stage("update", write_resource_system.after("first")); +/// # schedule.add_system(read_resource_system.label("first")); +/// # schedule.add_system(write_resource_system.after("first")); /// # schedule.run_once(&mut world); /// ``` pub trait Resource: Send + Sync + 'static {} diff --git a/crates/bevy_ecs/src/system/system_piping.rs b/crates/bevy_ecs/src/system/system_piping.rs index ca45cca7fb090..c05eac8bdd5cf 100644 --- a/crates/bevy_ecs/src/system/system_piping.rs +++ b/crates/bevy_ecs/src/system/system_piping.rs @@ -229,9 +229,7 @@ pub mod adapter { /// // Building a new schedule/app... /// # use bevy_ecs::schedule::SystemStage; /// # let mut sched = Schedule::default(); sched - /// # .add_stage(CoreStage::Update, SystemStage::parallel()) - /// .add_system_to_stage( - /// CoreStage::Update, + /// .add_system( /// // Panic if the load system returns an error. /// load_save_system.pipe(system_adapter::unwrap) /// ) @@ -266,9 +264,7 @@ pub mod adapter { /// // Building a new schedule/app... /// # use bevy_ecs::schedule::SystemStage; /// # let mut sched = Schedule::default(); sched - /// # .add_stage(CoreStage::Update, SystemStage::parallel()) - /// .add_system_to_stage( - /// CoreStage::Update, + /// .add_system( /// // Prints system information. /// data_pipe_system.pipe(system_adapter::info) /// ) @@ -299,8 +295,7 @@ pub mod adapter { /// // Building a new schedule/app... /// # use bevy_ecs::schedule::SystemStage; /// # let mut sched = Schedule::default(); sched - /// # .add_stage(CoreStage::Update, SystemStage::parallel()) - /// .add_system_to_stage( + /// .add_system( /// CoreStage::Update, /// // Prints debug data from system. /// parse_message_system.pipe(system_adapter::dbg) @@ -332,9 +327,7 @@ pub mod adapter { /// // Building a new schedule/app... /// # use bevy_ecs::schedule::SystemStage; /// # let mut sched = Schedule::default(); sched - /// # .add_stage(CoreStage::Update, SystemStage::parallel()) - /// .add_system_to_stage( - /// CoreStage::Update, + /// .add_system( /// // Prints system warning if system returns an error. /// warning_pipe_system.pipe(system_adapter::warn) /// ) @@ -367,8 +360,7 @@ pub mod adapter { /// // Building a new schedule/app... /// # use bevy_ecs::schedule::SystemStage; /// # let mut sched = Schedule::default(); sched - /// # .add_stage(CoreStage::Update, SystemStage::parallel()) - /// .add_system_to_stage( + /// .add_system( /// CoreStage::Update, /// // Prints system error if system fails. /// parse_error_message_system.pipe(system_adapter::error) @@ -409,8 +401,7 @@ pub mod adapter { /// // Building a new schedule/app... /// # use bevy_ecs::schedule::SystemStage; /// # let mut sched = Schedule::default(); sched - /// # .add_stage(CoreStage::Update, SystemStage::parallel()) - /// .add_system_to_stage( + /// .add_system( /// CoreStage::Update, /// // If the system fails, just move on and try again next frame. /// fallible_system.pipe(system_adapter::ignore) diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index a4b1d5535ffb8..ed46c532568e0 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -20,10 +20,11 @@ impl Plugin for GilrsPlugin { { Ok(gilrs) => { app.insert_non_send_resource(gilrs) - .add_startup_system_to_stage(StartupSet::PreStartup, gilrs_event_startup_system) - .add_system_to_stage( - CoreSet::PreUpdate, - gilrs_event_system.before(InputSystem), + .add_startup_system(gilrs_event_startup_system.label(StartupSet::PreStartup)) + .add_system( + gilrs_event_system + .before(InputSystem) + .label(CoreSet::PreUpdate), ); } Err(err) => error!("Failed to start Gilrs. {}", err), diff --git a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs index 93455dea142ae..a3d4ac51370ce 100644 --- a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs +++ b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs @@ -79,11 +79,10 @@ impl Default for ValidParentCheckPlugin { impl Plugin for ValidParentCheckPlugin { fn build(&self, app: &mut App) { - app.init_resource::>() - .add_system_to_stage( - CoreSet::Last, - check_hierarchy_component_has_valid_parent:: - .with_run_criteria(on_hierarchy_reports_enabled::), - ); + app.init_resource::>().add_system( + check_hierarchy_component_has_valid_parent:: + .with_run_criteria(on_hierarchy_reports_enabled::) + .label(CoreSet::Last), + ); } } diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index fe624cb899cba..c46fd4375bfad 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -56,15 +56,20 @@ impl Plugin for InputPlugin { .add_event::() .init_resource::>() .init_resource::>() - .add_system_to_stage(CoreSet::PreUpdate, keyboard_input_system.label(InputSystem)) + .add_system( + keyboard_input_system + .label(InputSystem) + .label(CoreSet::PreUpdate), + ) // mouse .add_event::() .add_event::() .add_event::() .init_resource::>() - .add_system_to_stage( - CoreSet::PreUpdate, - mouse_button_input_system.label(InputSystem), + .add_system( + mouse_button_input_system + .label(InputSystem) + .label(CoreSet::PreUpdate), ) // gamepad .add_event::() @@ -76,21 +81,22 @@ impl Plugin for InputPlugin { .init_resource::>() .init_resource::>() .init_resource::>() - .add_system_set_to_stage( - CoreSet::PreUpdate, + .add_system_set( SystemSet::new() .with_system(gamepad_event_system) .with_system(gamepad_button_event_system.after(gamepad_event_system)) .with_system(gamepad_axis_event_system.after(gamepad_event_system)) .with_system(gamepad_connection_system.after(gamepad_event_system)) - .label(InputSystem), + .label(InputSystem) + .label(CoreSet::PreUpdate), ) // touch .add_event::() .init_resource::() - .add_system_to_stage( - CoreSet::PreUpdate, - touch_screen_input_system.label(InputSystem), + .add_system( + touch_screen_input_system + .label(InputSystem) + .label(CoreSet::PreUpdate), ); // Register common types diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 3d31ab83d82d8..bab6df0acc371 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -141,27 +141,27 @@ impl Plugin for PbrPlugin { .init_resource::() .init_resource::() .add_plugin(ExtractResourcePlugin::::default()) - .add_system_to_stage( - CoreSet::PostUpdate, + .add_system( // NOTE: Clusters need to have been added before update_clusters is run so // add as an exclusive system add_clusters .at_start() - .label(SimulationLightSystems::AddClusters), + .label(SimulationLightSystems::AddClusters) + .label(CoreSet::PostUpdate), ) - .add_system_to_stage( - CoreSet::PostUpdate, + .add_system( assign_lights_to_clusters .label(SimulationLightSystems::AssignLightsToClusters) + .label(CoreSet::PostUpdate) .after(TransformSystem::TransformPropagate) .after(VisibilitySystems::CheckVisibility) .after(CameraUpdateSystem) .after(ModifiesWindows), ) - .add_system_to_stage( - CoreSet::PostUpdate, + .add_system( update_directional_light_frusta .label(SimulationLightSystems::UpdateLightFrusta) + .label(CoreSet::PostUpdate) // This must run after CheckVisibility because it relies on ComputedVisibility::is_visible() .after(VisibilitySystems::CheckVisibility) .after(TransformSystem::TransformPropagate) @@ -170,24 +170,24 @@ impl Plugin for PbrPlugin { // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. .ambiguous_with(update_spot_light_frusta), ) - .add_system_to_stage( - CoreSet::PostUpdate, + .add_system( update_point_light_frusta .label(SimulationLightSystems::UpdateLightFrusta) + .lable(CoreSet::PostUpdate) .after(TransformSystem::TransformPropagate) .after(SimulationLightSystems::AssignLightsToClusters), ) - .add_system_to_stage( - CoreSet::PostUpdate, + .add_system( update_spot_light_frusta .label(SimulationLightSystems::UpdateLightFrusta) + .label(CoreSet::PostUpdate) .after(TransformSystem::TransformPropagate) .after(SimulationLightSystems::AssignLightsToClusters), ) - .add_system_to_stage( - CoreSet::PostUpdate, + .add_system( check_light_mesh_visibility .label(SimulationLightSystems::CheckLightVisibility) + .label(CoreSet::PostUpdate) .after(TransformSystem::TransformPropagate) .after(SimulationLightSystems::UpdateLightFrusta) // NOTE: This MUST be scheduled AFTER the core renderer visibility check @@ -213,35 +213,39 @@ impl Plugin for PbrPlugin { }; render_app - .add_system_to_stage( - RenderStage::Extract, - render::extract_clusters.label(RenderLightSystems::ExtractClusters), + .add_system( + render::extract_clusters + .label(RenderLightSystems::ExtractClusters) + .label(RenderStage::Extract), ) - .add_system_to_stage( - RenderStage::Extract, - render::extract_lights.label(RenderLightSystems::ExtractLights), + .add_system( + render::extract_lights + .label(RenderLightSystems::ExtractLights) + .label(RenderStage::Extract), ) - .add_system_to_stage( - RenderStage::Prepare, + .add_system( // this is added as an exclusive system because it contributes new views. it must run (and have Commands applied) // _before_ the `prepare_views()` system is run. ideally this becomes a normal system when "stageless" features come out render::prepare_lights .at_start() - .label(RenderLightSystems::PrepareLights), + .label(RenderLightSystems::PrepareLights) + .label(RenderStage::Prepare), ) - .add_system_to_stage( - RenderStage::Prepare, + .add_system( // NOTE: This needs to run after prepare_lights. As prepare_lights is an exclusive system, // just adding it to the non-exclusive systems in the Prepare stage means it runs after // prepare_lights. - render::prepare_clusters.label(RenderLightSystems::PrepareClusters), + render::prepare_clusters + .label(RenderLightSystems::PrepareClusters) + .label(RenderStage::Prepare), ) - .add_system_to_stage( - RenderStage::Queue, - render::queue_shadows.label(RenderLightSystems::QueueShadows), + .add_system( + render::queue_shadows + .label(RenderLightSystems::QueueShadows) + .label(RenderStage::Queue), ) - .add_system_to_stage(RenderStage::Queue, render::queue_shadow_view_bind_group) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) + .add_system(render::queue_shadow_view_bind_group.label(RenderStage::Queue)) + .add_system(sort_phase_system::.label(RenderStage::PhaseSort)) .init_resource::() .init_resource::>() .init_resource::() diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 622659effd435..17401bb846bd1 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -173,12 +173,13 @@ where .init_resource::>() .init_resource::>() .init_resource::>>() - .add_system_to_stage(RenderStage::Extract, extract_materials::) - .add_system_to_stage( - RenderStage::Prepare, - prepare_materials::.after(PrepareAssetLabel::PreAssetPrepare), + .add_system(extract_materials::.label(RenderStage::Extract)) + .add_system( + prepare_materials:: + .after(PrepareAssetLabel::PreAssetPrepare) + .label(RenderStage::Prepare), ) - .add_system_to_stage(RenderStage::Queue, queue_material_meshes::); + .add_system(queue_material_meshes::.label(RenderStage::Queue)); } } } diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 92b65621922ac..372e64f1e9180 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -98,11 +98,11 @@ impl Plugin for MeshRenderPlugin { render_app .init_resource::() .init_resource::() - .add_system_to_stage(RenderStage::Extract, extract_meshes) - .add_system_to_stage(RenderStage::Extract, extract_skinned_meshes) - .add_system_to_stage(RenderStage::Prepare, prepare_skinned_meshes) - .add_system_to_stage(RenderStage::Queue, queue_mesh_bind_group) - .add_system_to_stage(RenderStage::Queue, queue_mesh_view_bind_groups); + .add_system(extract_meshes.label(RenderStage::Extract)) + .add_system(extract_skinned_meshes.label(RenderStage::Extract)) + .add_system(prepare_skinned_meshes.label(RenderStage::Prepare)) + .add_system(queue_mesh_bind_group.label(RenderStage::Queue)) + .add_system(queue_mesh_view_bind_groups.label(RenderStage::Queue)); } } } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 5339043913454..f3b2965a1d911 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -46,8 +46,8 @@ impl Plugin for WireframePlugin { .add_render_command::() .init_resource::() .init_resource::>() - .add_system_to_stage(RenderStage::Extract, extract_wireframes) - .add_system_to_stage(RenderStage::Queue, queue_wireframes); + .add_system(extract_wireframes.label(RenderStage::Extract)) + .add_system(queue_wireframes.label(RenderStage::Queue)); } } } diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index df81b87b261c8..1ebba855aa219 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -27,7 +27,7 @@ impl Plugin for CameraPlugin { .add_plugin(CameraProjectionPlugin::::default()); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_system_to_stage(RenderStage::Extract, extract_cameras); + render_app.add_system(extract_cameras.label(RenderStage::Extract)); let camera_driver_node = CameraDriverNode::new(&mut render_app.world); let mut render_graph = render_app.world.resource_mut::(); diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index 89e1f2bbab35c..724b16bceac8b 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -28,19 +28,19 @@ pub struct CameraUpdateSystem; impl Plugin for CameraProjectionPlugin { fn build(&self, app: &mut App) { app.register_type::() - .add_startup_system_to_stage( - StartupSet::PostStartup, + .add_startup_system( crate::camera::camera_system:: .label(CameraUpdateSystem) + .label(StartupSet::PostStartup) // We assume that each camera will only have one projection, // so we can ignore ambiguities with all other monomorphizations. // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. .ambiguous_with(CameraUpdateSystem), ) - .add_system_to_stage( - CoreSet::PostUpdate, + .add_system( crate::camera::camera_system:: .label(CameraUpdateSystem) + .label(CoreSet::PostUpdate) .after(ModifiesWindows) // We assume that each camera will only have one projection, // so we can ignore ambiguities with all other monomorphizations. diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index 7cae7d8065ed5..c42ea16f1ac53 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -81,7 +81,7 @@ impl Plugin for UniformComponentP if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .insert_resource(ComponentUniforms::::default()) - .add_system_to_stage(RenderStage::Prepare, prepare_uniform_components::); + .add_system(prepare_uniform_components::.label(RenderStage::Prepare)); } } } @@ -178,10 +178,9 @@ impl Plugin for ExtractComponentPlugin { fn build(&self, app: &mut App) { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { if self.only_extract_visible { - render_app - .add_system_to_stage(RenderStage::Extract, extract_visible_components::); + render_app.add_system(extract_visible_components::.label(RenderStage::Extract)); } else { - render_app.add_system_to_stage(RenderStage::Extract, extract_components::); + render_app.add_system(extract_components::.label(RenderStage::Extract)); } } } diff --git a/crates/bevy_render/src/extract_resource.rs b/crates/bevy_render/src/extract_resource.rs index af726c5c8a38d..ace9ece3cc13d 100644 --- a/crates/bevy_render/src/extract_resource.rs +++ b/crates/bevy_render/src/extract_resource.rs @@ -35,7 +35,7 @@ impl Default for ExtractResourcePlugin { impl Plugin for ExtractResourcePlugin { fn build(&self, app: &mut App) { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_system_to_stage(RenderStage::Extract, extract_resource::); + render_app.add_system(extract_resource::.label(RenderStage::Extract)); } } } diff --git a/crates/bevy_render/src/globals.rs b/crates/bevy_render/src/globals.rs index a0b160dd4b27d..11df70c83ead0 100644 --- a/crates/bevy_render/src/globals.rs +++ b/crates/bevy_render/src/globals.rs @@ -19,9 +19,9 @@ impl Plugin for GlobalsPlugin { render_app .init_resource::() .init_resource::

(&mut self, system: impl IntoSystemConfig

) -> &mut Self { - if let Some(default_schedule_label) = self.default_schedule_label { - if let Some(default_schedule) = self.schedules.get_mut(&default_schedule_label) { + let schedules = self.world.resource_mut::(); + + if let Some(default_schedule_label) = schedules.default_schedule_label { + if let Some(default_schedule) = schedules.get_mut(&default_schedule_label) { default_schedule.add_system(system); } else { panic!("Default schedule does not exist.") @@ -304,8 +304,10 @@ impl App { /// ); /// ``` pub fn add_systems

(&mut self, systems: impl IntoSystemConfigs

) -> &mut Self { - if let Some(default_schedule_label) = self.default_schedule_label { - if let Some(default_schedule) = self.schedules.get_mut(&default_schedule_label) { + let schedules = self.world.resource_mut::(); + + if let Some(default_schedule_label) = schedules.default_schedule_label { + if let Some(default_schedule) = schedules.get_mut(&default_schedule_label) { default_schedule.add_systems(systems); } else { panic!("Default schedule does not exist.") @@ -323,7 +325,9 @@ impl App { system: impl IntoSystemConfig

, schedule_label: impl ScheduleLabel, ) -> &mut Self { - if let Some(schedule) = self.schedules.get_mut(&schedule_label) { + let schedules = self.world.resource_mut::(); + + if let Some(schedule) = schedules.get_mut(&schedule_label) { schedule.add_system(system); } else { panic!("Provided schedule {schedule_label:?} does not exist.") @@ -338,7 +342,9 @@ impl App { systems: impl IntoSystemConfigs

, schedule_label: impl ScheduleLabel, ) -> &mut Self { - if let Some(schedule) = self.schedules.get_mut(&schedule_label) { + let schedules = self.world.resource_mut::(); + + if let Some(schedule) = schedules.get_mut(&schedule_label) { schedule.add_systems(systems); } else { panic!("Provided schedule {schedule_label:?} does not exist.") diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 0ef8ebd793c9f..55629b2063911 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -27,6 +27,8 @@ use crate::{ #[derive(Default, Resource)] pub struct Schedules { inner: HashMap, + /// The schedule that systems are added to by default + pub default_schedule: Option, } impl Schedules { @@ -34,6 +36,7 @@ impl Schedules { pub fn new() -> Self { Self { inner: HashMap::new(), + default_schedule: None, } } diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 6a37e1542122b..67d4bde3a2fbd 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -137,9 +137,9 @@ mod tests { change_detection::DetectChanges, component::{Component, Components}, entity::{Entities, Entity}, - prelude::{AnyOf, StageLabel}, + prelude::AnyOf, query::{Added, Changed, Or, With, Without}, - schedule::{Schedule, Stage, SystemStage}, + schedule::Schedule, system::{ Commands, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, QueryComponentError, RemovedComponents, Res, ResMut, Resource, System, SystemState, @@ -169,9 +169,6 @@ mod tests { #[derive(Component, Debug)] struct W(T); - #[derive(StageLabel)] - struct UpdateStage; - #[test] fn simple_system() { fn sys(query: Query<&A>) { @@ -190,9 +187,7 @@ mod tests { fn run_system>(world: &mut World, system: S) { let mut schedule = Schedule::default(); - let mut update = SystemStage::parallel(); - update.add_system(system); - schedule.add_stage(UpdateStage, update); + schedule.add_system(system); schedule.run(world); } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 9d87a08277ff6..ced078f7d3b55 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -19,7 +19,7 @@ use crate::{ event::{Event, Events}, ptr::UnsafeCellDeref, query::{DebugCheckedUnwrap, QueryState, ReadOnlyWorldQuery, WorldQuery}, - schedule::{ScheduleLabel, Schedules}, + schedule::{Schedule, ScheduleLabel, Schedules}, storage::{Column, ComponentSparseSet, ResourceData, SparseSet, Storages, TableRow}, system::Resource, }; @@ -1975,7 +1975,20 @@ impl World { // Schedule-related methods impl World { - /// Runs the [`Schedule`](crate::schedule::Schedule) associated with the `label`. + /// Runs the [`Schedule`] associated with the `label` a single time. + /// + /// The [`Schedule`] is fetched from the + pub fn add_schedule(&mut self, schedule: Schedule, label: impl ScheduleLabel) { + let mut schedules = self.resource_mut::(); + schedules.insert(label, schedule); + } + + /// Runs the [`Schedule`] associated with the `label` a single time. + /// + /// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label, + /// and system state is cached. + /// + /// For simple testing use cases, call [`Schedule::run(world)`](Schedule::run) instead. pub fn run_schedule(&mut self, label: impl ScheduleLabel) { if let Some(mut schedule) = self.resource_mut::().remove(&label) { schedule.run(self); From badcbfe52dba86d15d5109f91106399105f86703 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 12:55:21 -0500 Subject: [PATCH 026/247] Make sure new methods are pub --- crates/bevy_app/src/app.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index ba706cf486de3..4986891d3348e 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -5,7 +5,7 @@ use bevy_ecs::{ prelude::FromWorld, schedule::{ apply_system_buffers, BoxedScheduleLabel, IntoSystemConfig, IntoSystemConfigs, Schedule, - ScheduleLabel, Schedules, + ScheduleLabel, Schedules, States, }, system::Resource, world::World, @@ -228,7 +228,7 @@ impl App { /// Applies the function to the [`Schedule`] associated with `label`. /// /// **Note:** This will create the schedule if it does not already exist. - fn edit_schedule( + pub fn edit_schedule( &mut self, label: impl ScheduleLabel, f: impl FnMut(&mut Schedule), @@ -249,7 +249,7 @@ impl App { /// Adds [`State`] and [`NextState`] resources, [`OnEnter`] and [`OnExit`] schedules /// for each state variant, and an instance of [`apply_state_transition::`] in /// \ so that transitions happen before `Update`. - fn add_state(&mut self) -> &mut Self; + pub fn add_state(&mut self) -> &mut Self; /// Adds a system to the default system set and schedule of the app's [`Schedules`]. /// From 4fc9378d8772f52ded3c763dc4a98b210f07fddb Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 12:58:51 -0500 Subject: [PATCH 027/247] Compiler errors --- crates/bevy_app/src/app.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 4986891d3348e..ed79002b40f40 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -94,7 +94,7 @@ struct SubApp { impl SubApp { /// Runs the `SubApp`'s schedule. pub fn run(&mut self) { - let default_schedule = self.app.schedules.get_mut(self.app.default_schedule_label); + let default_schedule = self.app.default_schedule().unwrap(); self.app.world.run_schedule(default_schedule); self.app.world.clear_trackers(); } @@ -210,7 +210,9 @@ impl App { /// /// **Note:** This will create the schedule if it does not already exist. pub fn set_default_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self { - self.default_schedule_label = Some(Box::new(label)); + let mut schedules = self.world.resource_mut::(); + + schedules.default_schedule_label = Some(Box::new(label)); if self.schedules.get(&label).is_none() { self.schedules.insert(label, Schedule::new()); } @@ -220,8 +222,8 @@ impl App { /// Gets the label of the [`Schedule`] that will be modified by default when you call `App::add_system` /// and similar methods. - pub fn default_schedule(&mut self, label: impl ScheduleLabel) -> &BoxedScheduleLabel { - let mut schedules = self.world.resource::(); + pub fn default_schedule(&self) -> &Option { + let schedules = self.world.resource::(); &schedules.default_schedule_label } @@ -518,6 +520,8 @@ impl App { // Default set startup_schedule.set_default_set(CoreSet::Update); + + self } /// Setup the application to manage events of type `T`. From 3e0ed5cf16db85ac0038e8f20c944c144f755cbf Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 13:04:13 -0500 Subject: [PATCH 028/247] App::add_schedule --- crates/bevy_app/src/app.rs | 15 ++++++++++++--- crates/bevy_ecs/src/schedule/schedule.rs | 4 ++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index ed79002b40f40..8f7e3c4e2201a 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -401,6 +401,16 @@ impl App { self.add_systems_to_schedule(systems, CoreSchedule::Startup) } + /// Adds a new `schedule` to the [`App`] under the provided `label. + /// + /// See [`App::add_schedule`] to pass in a pre-constructed schedule. + pub fn add_schedule(&mut self, label: impl ScheduleLabel, schedule: Schedule) -> &mut Self { + let mut schedules = self.world.resource_mut::(); + schedules.insert(label, schedule); + + self + } + /// Adds standardized schedules and labels to an [`App`]. /// /// Adding these schedules is necessary to make some core engine features work. @@ -422,9 +432,8 @@ impl App { /// let app = App::empty().add_default_schedules(); /// ``` pub fn add_default_schedules(&mut self) -> &mut Self { - self.schedules - .insert(CoreSchedule::Startup, Schedule::new()); - self.schedules.insert(CoreSchedule::Main, Schedule::new()); + self.add_schedule(CoreSchedule::Startup, Schedule::new()); + self.add_schedule(CoreSchedule::Main, Schedule::new()); self.set_default_schedule(CoreSchedule::Main); diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 55629b2063911..257c5e55018ca 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -28,7 +28,7 @@ use crate::{ pub struct Schedules { inner: HashMap, /// The schedule that systems are added to by default - pub default_schedule: Option, + pub default_schedule_label: Option, } impl Schedules { @@ -36,7 +36,7 @@ impl Schedules { pub fn new() -> Self { Self { inner: HashMap::new(), - default_schedule: None, + default_schedule_label: None, } } From 979a959e608888e6c3118cfa565861ccbe69ffc4 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 13:05:27 -0500 Subject: [PATCH 029/247] Impl SCheduleLabel for CoreSchedule --- crates/bevy_app/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index 3ed2e5538297b..b73d09b4816e8 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -25,11 +25,12 @@ pub mod prelude { pub use crate::{app::App, CoreSet, DynamicPlugin, Plugin, PluginGroup, StartupSet}; } -use bevy_ecs::schedule::StageLabel; +use bevy_ecs::schedule::{ScheduleLabel, StageLabel}; /// The names of the default [`App`] schedules. /// /// The corresponding [`Schedule`](bevy_ecs::schedule::Schedule) objects are added by [`App::add_default_schedules`]. +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] pub enum CoreSchedule { /// The schedule that runs once when the app starts. Startup, From 1c53c3fe20251e1f52f0cd1d05af6d361d112b26 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 13:09:43 -0500 Subject: [PATCH 030/247] Add App::run_schedule --- crates/bevy_app/src/app.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 8f7e3c4e2201a..bc284bea2e4b4 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -411,6 +411,13 @@ impl App { self } + /// Runs the [`Schedule`] with the provided `label` on the app's [`World`] a single time. + pub fn run_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self { + self.world.run_schedule(label); + + self + } + /// Adds standardized schedules and labels to an [`App`]. /// /// Adding these schedules is necessary to make some core engine features work. From 7555b34bb964b5a33dcc4b894ef293894422a072 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 13:15:15 -0500 Subject: [PATCH 031/247] Add active schedule --- crates/bevy_app/src/app.rs | 20 ++++++++++++++++++-- crates/bevy_ecs/src/schedule/schedule.rs | 3 +++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index bc284bea2e4b4..3cac93258dfae 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -213,8 +213,8 @@ impl App { let mut schedules = self.world.resource_mut::(); schedules.default_schedule_label = Some(Box::new(label)); - if self.schedules.get(&label).is_none() { - self.schedules.insert(label, Schedule::new()); + if schedules.get(&label).is_none() { + schedules.insert(label, Schedule::new()); } self @@ -227,6 +227,22 @@ impl App { &schedules.default_schedule_label } + /// Sets the [`Schedule`] that will be run the next time that [`App::update`] or [`App::run`] is called. + pub fn set_active_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self { + let mut schedules = self.world.resource_mut::(); + + schedules.active_schedule_label = Some(Box::new(label)); + + self + } + + /// Gets the label of the [`Schedule`] that will be modified by default when you call `App::add_system` + /// and similar methods. + pub fn active_schedule(&self) -> &Option { + let schedules = self.world.resource::(); + &schedules.active_schedule_label + } + /// Applies the function to the [`Schedule`] associated with `label`. /// /// **Note:** This will create the schedule if it does not already exist. diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 257c5e55018ca..79bc5e090bb3a 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -29,6 +29,8 @@ pub struct Schedules { inner: HashMap, /// The schedule that systems are added to by default pub default_schedule_label: Option, + /// The currently active `Schedule`, which should be run next. + pub active_schedule_label: Option, } impl Schedules { @@ -37,6 +39,7 @@ impl Schedules { Self { inner: HashMap::new(), default_schedule_label: None, + active_schedule_label: None, } } From f3f94ca8f62ab727814b936551ef4af49cdd11da Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 13:18:56 -0500 Subject: [PATCH 032/247] Call active schedule in App::update() --- crates/bevy_app/src/app.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 3cac93258dfae..636c9ac5bfb00 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -164,10 +164,16 @@ impl App { /// This method also updates sub apps. /// /// See [`add_sub_app`](Self::add_sub_app) and [`run_once`](Schedule::run_once) for more details. + /// + /// # Panics + /// + /// The active schedule of the app must be set before this method is called. pub fn update(&mut self) { #[cfg(feature = "trace")] let _bevy_frame_update_span = info_span!("frame").entered(); - self.schedule.run(&mut self.world); + + let active_schedule = self.active_schedule().unwrap(); + self.run_schedule(active_schedule); for sub_app in self.sub_apps.values_mut() { sub_app.extract(&mut self.world); @@ -442,7 +448,8 @@ impl App { /// /// The schedules are defined in the [`CoreSchedule`] enum. /// - /// The [default schedule](App::set_default_schedule) becomes [`CoreSchedule::Main`]. + /// The [default schedule](App::set_default_schedule) is set to [`CoreSchedule::Main`]. + /// The [active schedule] is set to [`CoreSchedule::Startup`]. /// /// You can also add standardized system sets to these schedules using [`App::add_default_sets`], /// which must be called *after* this method as it relies on these schedules existing. @@ -459,6 +466,7 @@ impl App { self.add_schedule(CoreSchedule::Main, Schedule::new()); self.set_default_schedule(CoreSchedule::Main); + self.set_active_schedule(CoreSchedule::Startup); self } From b11cf25fa4354163863048f7f5f3621d4fddf3f0 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 13:22:22 -0500 Subject: [PATCH 033/247] Transition out of the startup schedule --- crates/bevy_app/src/app.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 636c9ac5bfb00..bb97d0ce60fb2 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -175,6 +175,12 @@ impl App { let active_schedule = self.active_schedule().unwrap(); self.run_schedule(active_schedule); + // After the startup schedule has been called once, transition to the main schedule + let boxed_startup_label: BoxedScheduleLabel = Box::new(CoreSchedule::Startup); + if self.active_schedule().unwrap() == boxed_startup_label { + self.set_active_schedule(CoreSchedule::Main); + } + for sub_app in self.sub_apps.values_mut() { sub_app.extract(&mut self.world); sub_app.run(); From af54fe53506c48382e36f03b6f489997625829a5 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 16:31:10 -0500 Subject: [PATCH 034/247] Remove StageLabel derive --- crates/bevy_app/src/lib.rs | 10 ++++++---- crates/bevy_asset/src/assets.rs | 6 +++--- crates/bevy_asset/src/lib.rs | 12 ++++++------ crates/bevy_core_pipeline/src/bloom/mod.rs | 6 +++--- crates/bevy_core_pipeline/src/core_2d/mod.rs | 8 ++++---- crates/bevy_core_pipeline/src/core_3d/mod.rs | 12 ++++++------ crates/bevy_core_pipeline/src/fxaa/mod.rs | 4 ++-- .../bevy_core_pipeline/src/tonemapping/mod.rs | 4 ++-- crates/bevy_core_pipeline/src/upscaling/mod.rs | 4 ++-- crates/bevy_ecs/README.md | 4 ---- crates/bevy_ecs/macros/src/lib.rs | 13 ------------- crates/bevy_ecs/src/schedule/label.rs | 8 +------- crates/bevy_pbr/src/lib.rs | 16 ++++++++-------- crates/bevy_pbr/src/material.rs | 8 ++++---- crates/bevy_pbr/src/render/mesh.rs | 12 ++++++------ crates/bevy_pbr/src/wireframe.rs | 6 +++--- crates/bevy_render/src/camera/mod.rs | 4 ++-- crates/bevy_render/src/extract_component.rs | 8 ++++---- crates/bevy_render/src/extract_resource.rs | 4 ++-- crates/bevy_render/src/globals.rs | 8 ++++---- crates/bevy_render/src/lib.rs | 18 +++++++++--------- crates/bevy_render/src/render_asset.rs | 6 +++--- crates/bevy_render/src/texture/mod.rs | 4 ++-- crates/bevy_render/src/view/mod.rs | 6 +++--- crates/bevy_render/src/view/window.rs | 6 +++--- crates/bevy_sprite/src/lib.rs | 8 ++++---- crates/bevy_sprite/src/mesh2d/material.rs | 8 ++++---- crates/bevy_sprite/src/mesh2d/mesh.rs | 8 ++++---- crates/bevy_text/src/lib.rs | 4 ++-- crates/bevy_ui/src/render/mod.rs | 16 ++++++++-------- examples/2d/mesh2d_manual.rs | 6 +++--- examples/shader/compute_shader_game_of_life.rs | 4 ++-- examples/shader/shader_instancing.rs | 6 +++--- examples/stress_tests/many_lights.rs | 6 +++--- 34 files changed, 121 insertions(+), 142 deletions(-) diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index b73d09b4816e8..c5f51e9f6da57 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -25,7 +25,7 @@ pub mod prelude { pub use crate::{app::App, CoreSet, DynamicPlugin, Plugin, PluginGroup, StartupSet}; } -use bevy_ecs::schedule::{ScheduleLabel, StageLabel}; +use bevy_ecs::schedule::{ScheduleLabel, SystemLabel}; /// The names of the default [`App`] schedules. /// @@ -40,8 +40,8 @@ pub enum CoreSchedule { /// The names of the default [`App`] system sets. /// -/// The relative [`SystemSet`](bevy_ecs::schedule::SystemSet) are added by [`App::add_default_sets`]. -#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] +/// The corresponding [`SystemSets`](bevy_ecs::schedule::SystemSet) are added by [`App::add_default_sets`]. +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] pub enum CoreSet { /// Runs before all other app stages. First, @@ -56,7 +56,9 @@ pub enum CoreSet { } /// The names of the default [`App`] startup stages. -#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] +/// +/// The corresponding [`SystemSets`](bevy_ecs::schedule::SystemSet) are added by [`App::add_default_sets`]. +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] pub enum StartupSet { /// Runs once before [`StartupSet::Startup`]. PreStartup, diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index f28553fc50c58..dd35492b0d10d 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -1,5 +1,5 @@ use crate::{ - update_asset_storage_system, Asset, AssetLoader, AssetServer, AssetStage, Handle, HandleId, + update_asset_storage_system, Asset, AssetLoader, AssetServer, AssetSet, Handle, HandleId, RefChange, ReflectAsset, ReflectHandle, }; use bevy_app::{App, AppTypeRegistry}; @@ -335,8 +335,8 @@ impl AddAsset for App { }; self.insert_resource(assets) - .add_system(Assets::::asset_event_system.in_set(AssetStage::AssetEvents)) - .add_system(update_asset_storage_system::.in_set(AssetStage::LoadAssets)) + .add_system(Assets::::asset_event_system.in_set(AssetSet::AssetEvents)) + .add_system(update_asset_storage_system::.in_set(AssetSet::LoadAssets)) .register_type::>() .add_event::>() } diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 927bb883ab9ea..ab48d405a4eb5 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -47,11 +47,11 @@ pub use path::*; pub use reflect::*; use bevy_app::{prelude::Plugin, App}; -use bevy_ecs::schedule::{StageLabel, SystemStage}; +use bevy_ecs::schedule::SystemLabel; /// The names of asset stages in an [`App`] schedule. -#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] -pub enum AssetStage { +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +pub enum AssetSet { /// The stage where asset storages are updated. LoadAssets, /// The stage where asset events are generated. @@ -107,12 +107,12 @@ impl Plugin for AssetPlugin { app.add_stage_before( bevy_app::CoreSet::PreUpdate, - AssetStage::LoadAssets, + AssetSet::LoadAssets, SystemStage::parallel(), ) .add_stage_after( bevy_app::CoreSet::PostUpdate, - AssetStage::AssetEvents, + AssetSet::AssetEvents, SystemStage::parallel(), ) .register_type::() @@ -122,6 +122,6 @@ impl Plugin for AssetPlugin { feature = "filesystem_watcher", all(not(target_arch = "wasm32"), not(target_os = "android")) ))] - app.add_system(io::filesystem_watcher_system.in_set(AssetStage::LoadAssets)); + app.add_system(io::filesystem_watcher_system.in_set(AssetSet::LoadAssets)); } } diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index ed450d4362513..bec9eedc0b394 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -21,7 +21,7 @@ use bevy_render::{ renderer::{RenderContext, RenderDevice}, texture::{CachedTexture, TextureCache}, view::ViewTarget, - RenderApp, RenderStage, + RenderApp, RenderSet, }; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; @@ -48,8 +48,8 @@ impl Plugin for BloomPlugin { render_app .init_resource::() - .add_system(prepare_bloom_textures.in_set(RenderStage::Prepare)) - .add_system(queue_bloom_bind_groups.in_set(RenderStage::Queue)); + .add_system(prepare_bloom_textures.in_set(RenderSet::Prepare)) + .add_system(queue_bloom_bind_groups.in_set(RenderSet::Queue)); { let bloom_node = BloomNode::new(&mut render_app.world); diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index fcadf95cfbfac..073bfc0dc8e6e 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -30,7 +30,7 @@ use bevy_render::{ DrawFunctionId, DrawFunctions, PhaseItem, RenderPhase, }, render_resource::CachedRenderPipelineId, - Extract, RenderApp, RenderStage, + Extract, RenderApp, RenderSet, }; use bevy_utils::FloatOrd; use std::ops::Range; @@ -51,11 +51,11 @@ impl Plugin for Core2dPlugin { render_app .init_resource::>() - .add_system(extract_core_2d_camera_phases.in_set(RenderStage::Extract)) - .add_system(sort_phase_system::.in_set(RenderStage::PhaseSort)) + .add_system(extract_core_2d_camera_phases.in_set(RenderSet::Extract)) + .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) .add_system( batch_phase_system:: - .after(sort_phase_system::.in_set(RenderStage::PhaseSort)), + .after(sort_phase_system::.in_set(RenderSet::PhaseSort)), ); let pass_node_2d = MainPass2dNode::new(&mut render_app.world); diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 8ef1c41ec2c2a..715b20cc11ad8 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -39,7 +39,7 @@ use bevy_render::{ renderer::RenderDevice, texture::TextureCache, view::ViewDepthTexture, - Extract, RenderApp, RenderStage, + Extract, RenderApp, RenderSet, }; use bevy_utils::{FloatOrd, HashMap}; @@ -62,11 +62,11 @@ impl Plugin for Core3dPlugin { .init_resource::>() .init_resource::>() .init_resource::>() - .add_system(extract_core_3d_camera_phases.in_set(RenderStage::Extract)) - .add_system(prepare_core_3d_depth_textures.in_set(RenderStage::Prepare)) - .add_system(sort_phase_system::.in_set(RenderStage::PhaseSort)) - .add_system(sort_phase_system::.in_set(RenderStage::PhaseSort)) - .add_system(sort_phase_system::.in_set(RenderStage::PhaseSort)); + .add_system(extract_core_3d_camera_phases.in_set(RenderSet::Extract)) + .add_system(prepare_core_3d_depth_textures.in_set(RenderSet::Prepare)) + .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) + .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) + .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)); let pass_node_3d = MainPass3dNode::new(&mut render_app.world); let tonemapping = TonemappingNode::new(&mut render_app.world); diff --git a/crates/bevy_core_pipeline/src/fxaa/mod.rs b/crates/bevy_core_pipeline/src/fxaa/mod.rs index b7978aba9b224..e9ff0bac2b138 100644 --- a/crates/bevy_core_pipeline/src/fxaa/mod.rs +++ b/crates/bevy_core_pipeline/src/fxaa/mod.rs @@ -12,7 +12,7 @@ use bevy_render::{ renderer::RenderDevice, texture::BevyDefault, view::{ExtractedView, ViewTarget}, - RenderApp, RenderStage, + RenderApp, RenderSet, }; mod node; @@ -95,7 +95,7 @@ impl Plugin for FxaaPlugin { render_app .init_resource::() .init_resource::>() - .add_system(prepare_fxaa_pipelines.in_set(RenderStage::Prepare)); + .add_system(prepare_fxaa_pipelines.in_set(RenderSet::Prepare)); { let fxaa_node = FxaaNode::new(&mut render_app.world); diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index 63a39782fc68f..409b6095c7b62 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -8,7 +8,7 @@ use bevy_render::camera::Camera; use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin}; use bevy_render::renderer::RenderDevice; use bevy_render::view::ViewTarget; -use bevy_render::{render_resource::*, RenderApp, RenderStage}; +use bevy_render::{render_resource::*, RenderApp, RenderSet}; mod node; @@ -45,7 +45,7 @@ impl Plugin for TonemappingPlugin { render_app .init_resource::() .init_resource::>() - .add_system(queue_view_tonemapping_pipelines.in_set(RenderStage::Queue)); + .add_system(queue_view_tonemapping_pipelines.in_set(RenderSet::Queue)); } } } diff --git a/crates/bevy_core_pipeline/src/upscaling/mod.rs b/crates/bevy_core_pipeline/src/upscaling/mod.rs index 3124174f06875..b16dacab09c60 100644 --- a/crates/bevy_core_pipeline/src/upscaling/mod.rs +++ b/crates/bevy_core_pipeline/src/upscaling/mod.rs @@ -5,7 +5,7 @@ use bevy_ecs::prelude::*; use bevy_reflect::TypeUuid; use bevy_render::renderer::RenderDevice; use bevy_render::view::ViewTarget; -use bevy_render::{render_resource::*, RenderApp, RenderStage}; +use bevy_render::{render_resource::*, RenderApp, RenderSet}; mod node; @@ -29,7 +29,7 @@ impl Plugin for UpscalingPlugin { render_app .init_resource::() .init_resource::>() - .add_system(queue_view_upscaling_pipelines.in_set(RenderStage::Queue)); + .add_system(queue_view_upscaling_pipelines.in_set(RenderSet::Queue)); } } } diff --git a/crates/bevy_ecs/README.md b/crates/bevy_ecs/README.md index 8f9c6374fe4e2..ced1a275876b2 100644 --- a/crates/bevy_ecs/README.md +++ b/crates/bevy_ecs/README.md @@ -148,10 +148,6 @@ fn main() { // Create a new Schedule, which defines an execution strategy for Systems let mut schedule = Schedule::default(); - // Define a unique public name for a new Stage. - #[derive(StageLabel)] - pub struct UpdateLabel; - // Add a Stage to our schedule. Each Stage in a schedule runs all of its systems // before moving on to the next Stage schedule.add_stage(UpdateLabel, SystemStage::parallel() diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 7b7e87fd98c09..6259799f3359d 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -539,19 +539,6 @@ pub fn derive_system_label(input: TokenStream) -> TokenStream { derive_label(input, &trait_path, "system_label") } -/// Generates an impl of the `StageLabel` trait. -/// -/// This works only for unit structs, or enums with only unit variants. -/// You may force a struct or variant to behave as if it were fieldless with `#[stage_label(ignore_fields)]`. -#[proc_macro_derive(StageLabel, attributes(stage_label))] -pub fn derive_stage_label(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let mut trait_path = bevy_ecs_path(); - trait_path.segments.push(format_ident!("schedule").into()); - trait_path.segments.push(format_ident!("StageLabel").into()); - derive_label(input, &trait_path, "stage_label") -} - /// Generates an impl of the `RunCriteriaLabel` trait. /// /// This works only for unit structs, or enums with only unit variants. diff --git a/crates/bevy_ecs/src/schedule/label.rs b/crates/bevy_ecs/src/schedule/label.rs index c2f4d1fbfab7e..8ccb6ad8b4975 100644 --- a/crates/bevy_ecs/src/schedule/label.rs +++ b/crates/bevy_ecs/src/schedule/label.rs @@ -1,12 +1,6 @@ -pub use bevy_ecs_macros::{RunCriteriaLabel, StageLabel, SystemLabel}; +pub use bevy_ecs_macros::{RunCriteriaLabel, SystemLabel}; use bevy_utils::define_label; -define_label!( - /// A strongly-typed class of labels used to identify [`Stage`](crate::schedule::Stage)s. - StageLabel, - /// Strongly-typed identifier for a [`StageLabel`]. - StageLabelId, -); define_label!( /// A strongly-typed class of labels used to identify [`System`](crate::system::System)s. SystemLabel, diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 610b6a0381a93..c164370e1eee1 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -49,7 +49,7 @@ use bevy_render::{ render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions}, render_resource::{Shader, SpecializedMeshPipelines}, view::VisibilitySystems, - RenderApp, RenderStage, + RenderApp, RenderSet, }; use bevy_transform::TransformSystem; @@ -216,12 +216,12 @@ impl Plugin for PbrPlugin { .add_system( render::extract_clusters .in_set(RenderLightSystems::ExtractClusters) - .in_set(RenderStage::Extract), + .in_set(RenderSet::Extract), ) .add_system( render::extract_lights .in_set(RenderLightSystems::ExtractLights) - .in_set(RenderStage::Extract), + .in_set(RenderSet::Extract), ) .add_system( // this is added as an exclusive system because it contributes new views. it must run (and have Commands applied) @@ -229,7 +229,7 @@ impl Plugin for PbrPlugin { render::prepare_lights .at_start() .in_set(RenderLightSystems::PrepareLights) - .in_set(RenderStage::Prepare), + .in_set(RenderSet::Prepare), ) .add_system( // NOTE: This needs to run after prepare_lights. As prepare_lights is an exclusive system, @@ -237,15 +237,15 @@ impl Plugin for PbrPlugin { // prepare_lights. render::prepare_clusters .in_set(RenderLightSystems::PrepareClusters) - .in_set(RenderStage::Prepare), + .in_set(RenderSet::Prepare), ) .add_system( render::queue_shadows .in_set(RenderLightSystems::QueueShadows) - .in_set(RenderStage::Queue), + .in_set(RenderSet::Queue), ) - .add_system(render::queue_shadow_view_bind_group.in_set(RenderStage::Queue)) - .add_system(sort_phase_system::.in_set(RenderStage::PhaseSort)) + .add_system(render::queue_shadow_view_bind_group.in_set(RenderSet::Queue)) + .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) .init_resource::() .init_resource::>() .init_resource::() diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index e8c9901b93ef8..70b2f72a4d4b3 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -37,7 +37,7 @@ use bevy_render::{ renderer::RenderDevice, texture::FallbackImage, view::{ExtractedView, Msaa, VisibleEntities}, - Extract, RenderApp, RenderStage, + Extract, RenderApp, RenderSet, }; use bevy_utils::{tracing::error, HashMap, HashSet}; use std::hash::Hash; @@ -173,13 +173,13 @@ where .init_resource::>() .init_resource::>() .init_resource::>>() - .add_system(extract_materials::.in_set(RenderStage::Extract)) + .add_system(extract_materials::.in_set(RenderSet::Extract)) .add_system( prepare_materials:: .after(PrepareAssetLabel::PreAssetPrepare) - .in_set(RenderStage::Prepare), + .in_set(RenderSet::Prepare), ) - .add_system(queue_material_meshes::.in_set(RenderStage::Queue)); + .add_system(queue_material_meshes::.in_set(RenderSet::Queue)); } } } diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 40ac887475b02..28a6617e539ce 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -27,7 +27,7 @@ use bevy_render::{ BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, }, view::{ComputedVisibility, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms}, - Extract, RenderApp, RenderStage, + Extract, RenderApp, RenderSet, }; use bevy_transform::components::GlobalTransform; use std::num::NonZeroU64; @@ -98,11 +98,11 @@ impl Plugin for MeshRenderPlugin { render_app .init_resource::() .init_resource::() - .add_system(extract_meshes.in_set(RenderStage::Extract)) - .add_system(extract_skinned_meshes.in_set(RenderStage::Extract)) - .add_system(prepare_skinned_meshes.in_set(RenderStage::Prepare)) - .add_system(queue_mesh_bind_group.in_set(RenderStage::Queue)) - .add_system(queue_mesh_view_bind_groups.in_set(RenderStage::Queue)); + .add_system(extract_meshes.in_set(RenderSet::Extract)) + .add_system(extract_skinned_meshes.in_set(RenderSet::Extract)) + .add_system(prepare_skinned_meshes.in_set(RenderSet::Prepare)) + .add_system(queue_mesh_bind_group.in_set(RenderSet::Queue)) + .add_system(queue_mesh_view_bind_groups.in_set(RenderSet::Queue)); } } } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 46bf1ed973056..65a478e50bce9 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -17,7 +17,7 @@ use bevy_render::{ SpecializedMeshPipelineError, SpecializedMeshPipelines, }, view::{ExtractedView, Msaa, VisibleEntities}, - RenderApp, RenderStage, + RenderApp, RenderSet, }; use bevy_utils::tracing::error; @@ -46,8 +46,8 @@ impl Plugin for WireframePlugin { .add_render_command::() .init_resource::() .init_resource::>() - .add_system(extract_wireframes.in_set(RenderStage::Extract)) - .add_system(queue_wireframes.in_set(RenderStage::Queue)); + .add_system(extract_wireframes.in_set(RenderSet::Extract)) + .add_system(queue_wireframes.in_set(RenderSet::Queue)); } } } diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index 2f525cbef0deb..feb036296c1da 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -7,7 +7,7 @@ pub use camera::*; pub use camera_driver_node::*; pub use projection::*; -use crate::{render_graph::RenderGraph, RenderApp, RenderStage}; +use crate::{render_graph::RenderGraph, RenderApp, RenderSet}; use bevy_app::{App, Plugin}; #[derive(Default)] @@ -27,7 +27,7 @@ impl Plugin for CameraPlugin { .add_plugin(CameraProjectionPlugin::::default()); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_system(extract_cameras.in_set(RenderStage::Extract)); + render_app.add_system(extract_cameras.in_set(RenderSet::Extract)); let camera_driver_node = CameraDriverNode::new(&mut render_app.world); let mut render_graph = render_app.world.resource_mut::(); diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index 29bd790b507df..d0d8284e66a34 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -2,7 +2,7 @@ use crate::{ render_resource::{encase::internal::WriteInto, DynamicUniformBuffer, ShaderType}, renderer::{RenderDevice, RenderQueue}, view::ComputedVisibility, - Extract, RenderApp, RenderStage, + Extract, RenderApp, RenderSet, }; use bevy_app::{App, Plugin}; use bevy_asset::{Asset, Handle}; @@ -81,7 +81,7 @@ impl Plugin for UniformComponentP if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .insert_resource(ComponentUniforms::::default()) - .add_system(prepare_uniform_components::.in_set(RenderStage::Prepare)); + .add_system(prepare_uniform_components::.in_set(RenderSet::Prepare)); } } } @@ -178,9 +178,9 @@ impl Plugin for ExtractComponentPlugin { fn build(&self, app: &mut App) { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { if self.only_extract_visible { - render_app.add_system(extract_visible_components::.in_set(RenderStage::Extract)); + render_app.add_system(extract_visible_components::.in_set(RenderSet::Extract)); } else { - render_app.add_system(extract_components::.in_set(RenderStage::Extract)); + render_app.add_system(extract_components::.in_set(RenderSet::Extract)); } } } diff --git a/crates/bevy_render/src/extract_resource.rs b/crates/bevy_render/src/extract_resource.rs index bcda79a94b9c6..10f6a1d901e4e 100644 --- a/crates/bevy_render/src/extract_resource.rs +++ b/crates/bevy_render/src/extract_resource.rs @@ -7,7 +7,7 @@ use bevy_ecs::system::Local; use bevy_ecs::system::{Commands, Res, ResMut, Resource}; pub use bevy_render_macros::ExtractResource; -use crate::{Extract, RenderApp, RenderStage}; +use crate::{Extract, RenderApp, RenderSet}; /// Describes how a resource gets extracted for rendering. /// @@ -35,7 +35,7 @@ impl Default for ExtractResourcePlugin { impl Plugin for ExtractResourcePlugin { fn build(&self, app: &mut App) { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_system(extract_resource::.in_set(RenderStage::Extract)); + render_app.add_system(extract_resource::.in_set(RenderSet::Extract)); } } } diff --git a/crates/bevy_render/src/globals.rs b/crates/bevy_render/src/globals.rs index 56c3bab75da2f..c9f3164d5ebe3 100644 --- a/crates/bevy_render/src/globals.rs +++ b/crates/bevy_render/src/globals.rs @@ -2,7 +2,7 @@ use crate::{ extract_resource::ExtractResource, render_resource::{ShaderType, UniformBuffer}, renderer::{RenderDevice, RenderQueue}, - Extract, RenderApp, RenderStage, + Extract, RenderApp, RenderSet, }; use bevy_app::{App, Plugin}; use bevy_core::FrameCount; @@ -19,9 +19,9 @@ impl Plugin for GlobalsPlugin { render_app .init_resource::() .init_resource:: { .init_resource::>() .init_resource::>() .init_resource::>() - .add_system(extract_render_asset::.in_set(RenderStage::Extract)) - .add_system(prepare_asset_system.in_set(RenderStage::Prepare)); + .add_system(extract_render_asset::.in_set(RenderSet::Extract)) + .add_system(prepare_asset_system.in_set(RenderSet::Prepare)); } } } diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index eebb7d04ef68f..a950114e2e2c1 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -29,7 +29,7 @@ pub use texture_cache::*; use crate::{ render_asset::{PrepareAssetLabel, RenderAssetPlugin}, renderer::RenderDevice, - RenderApp, RenderStage, + RenderApp, RenderSet, }; use bevy_app::{App, Plugin}; use bevy_asset::{AddAsset, Assets}; @@ -102,7 +102,7 @@ impl Plugin for ImagePlugin { .insert_resource(DefaultImageSampler(default_sampler)) .init_resource::() .init_resource::() - .add_system(update_texture_cache_system.in_set(RenderStage::Cleanup)); + .add_system(update_texture_cache_system.in_set(RenderSet::Cleanup)); } } } diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 3343c5417da56..d552a52947889 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -13,7 +13,7 @@ use crate::{ render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView}, renderer::{RenderDevice, RenderQueue}, texture::{BevyDefault, TextureCache}, - RenderApp, RenderStage, + RenderApp, RenderSet, }; use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; @@ -45,11 +45,11 @@ impl Plugin for ViewPlugin { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() - .add_system(prepare_view_uniforms.in_set(RenderStage::Prepare)) + .add_system(prepare_view_uniforms.in_set(RenderSet::Prepare)) .add_system( prepare_view_targets .after(WindowSystem::Prepare) - .in_set(RenderStage::Prepare), + .in_set(RenderSet::Prepare), ); } } diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 20a3cd482f145..e5ff0a91a9067 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -1,7 +1,7 @@ use crate::{ render_resource::TextureView, renderer::{RenderAdapter, RenderDevice, RenderInstance}, - Extract, RenderApp, RenderStage, + Extract, RenderApp, RenderSet, }; use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; @@ -30,11 +30,11 @@ impl Plugin for WindowRenderPlugin { .init_resource::() .init_resource::() .init_non_send_resource::() - .add_system(extract_windows.in_set(RenderStage::Extract)) + .add_system(extract_windows.in_set(RenderSet::Extract)) .add_system( prepare_windows .in_set(WindowSystem::Prepare) - .in_set(RenderStage::Prepare), + .in_set(RenderSet::Prepare), ); } } diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index bc8ae92c2ad7d..019d6a90b9823 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -34,7 +34,7 @@ use bevy_reflect::TypeUuid; use bevy_render::{ render_phase::AddRenderCommand, render_resource::{Shader, SpecializedRenderPipelines}, - RenderApp, RenderStage, + RenderApp, RenderSet, }; #[derive(Default)] @@ -73,10 +73,10 @@ impl Plugin for SpritePlugin { .add_system( render::extract_sprites .in_set(SpriteSystem::ExtractSprites) - .in_set(RenderStage::Extract), + .in_set(RenderSet::Extract), ) - .add_system(render::extract_sprite_events.in_set(RenderStage::Extract)) - .add_system(queue_sprites.in_set(RenderStage::Queue)); + .add_system(render::extract_sprite_events.in_set(RenderSet::Extract)) + .add_system(queue_sprites.in_set(RenderSet::Queue)); }; } } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 089af92c20846..2b06f6a6fa072 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -32,7 +32,7 @@ use bevy_render::{ renderer::RenderDevice, texture::FallbackImage, view::{ComputedVisibility, ExtractedView, Msaa, Visibility, VisibleEntities}, - Extract, RenderApp, RenderStage, + Extract, RenderApp, RenderSet, }; use bevy_transform::components::{GlobalTransform, Transform}; use bevy_utils::{FloatOrd, HashMap, HashSet}; @@ -160,13 +160,13 @@ where .init_resource::>() .init_resource::>() .init_resource::>>() - .add_system(extract_materials_2d::.in_set(RenderStage::Extract)) + .add_system(extract_materials_2d::.in_set(RenderSet::Extract)) .add_system( prepare_materials_2d:: .after(PrepareAssetLabel::PreAssetPrepare) - .in_set(RenderStage::Prepare), + .in_set(RenderSet::Prepare), ) - .add_system(queue_material2d_meshes::.in_set(RenderStage::Queue)); + .add_system(queue_material2d_meshes::.in_set(RenderSet::Queue)); } } } diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 54d94d75e3138..a8a4dbc2d0bab 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -21,7 +21,7 @@ use bevy_render::{ view::{ ComputedVisibility, ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, }, - Extract, RenderApp, RenderStage, + Extract, RenderApp, RenderSet, }; use bevy_transform::components::GlobalTransform; @@ -102,9 +102,9 @@ impl Plugin for Mesh2dRenderPlugin { render_app .init_resource::() .init_resource::>() - .add_system(extract_mesh2d.in_set(RenderStage::Extract)) - .add_system(queue_mesh2d_bind_group.in_set(RenderStage::Queue)) - .add_system(queue_mesh2d_view_bind_groups.in_set(RenderStage::Queue)); + .add_system(extract_mesh2d.in_set(RenderSet::Extract)) + .add_system(queue_mesh2d_bind_group.in_set(RenderSet::Queue)) + .add_system(queue_mesh2d_view_bind_groups.in_set(RenderSet::Queue)); } } } diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 4d2d3aaf29236..823cf7111a3d6 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -26,7 +26,7 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_asset::AddAsset; use bevy_ecs::{schedule::IntoSystemDescriptor, system::Resource}; -use bevy_render::{camera::CameraUpdateSystem, RenderApp, RenderStage}; +use bevy_render::{camera::CameraUpdateSystem, RenderApp, RenderSet}; use bevy_sprite::SpriteSystem; use bevy_window::ModifiesWindows; use std::num::NonZeroUsize; @@ -95,7 +95,7 @@ impl Plugin for TextPlugin { render_app.add_system( extract_text2d_sprite .after(SpriteSystem::ExtractSprites) - .in_set(RenderStage::Extract), + .in_set(RenderSet::Extract), ); } } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index c6577053e897e..b55e83a9c2d87 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -22,7 +22,7 @@ use bevy_render::{ renderer::{RenderDevice, RenderQueue}, texture::Image, view::{ComputedVisibility, ExtractedView, ViewUniforms}, - Extract, RenderApp, RenderStage, + Extract, RenderApp, RenderSet, }; use bevy_sprite::{SpriteAssetEvents, TextureAtlas}; use bevy_text::{Text, TextLayoutInfo}; @@ -71,21 +71,21 @@ pub fn build_ui_render(app: &mut App) { .init_resource::() .init_resource::>() .add_render_command::() - .add_system(extract_default_ui_camera_view::.in_set(RenderStage::Extract)) - .add_system(extract_default_ui_camera_view::.in_set(RenderStage::Extract)) + .add_system(extract_default_ui_camera_view::.in_set(RenderSet::Extract)) + .add_system(extract_default_ui_camera_view::.in_set(RenderSet::Extract)) .add_system( extract_uinodes .in_set(RenderUiSystem::ExtractNode) - .in_set(RenderStage::Extract), + .in_set(RenderSet::Extract), ) .add_system( extract_text_uinodes .after(RenderUiSystem::ExtractNode) - .in_set(RenderStage::Extract), + .in_set(RenderSet::Extract), ) - .add_system(prepare_uinodes.in_set(RenderStage::Prepare)) - .add_system(queue_uinodes.in_set(RenderStage::Queue)) - .add_system(sort_phase_system::.in_set(RenderStage::PhaseSort)); + .add_system(prepare_uinodes.in_set(RenderSet::Prepare)) + .add_system(queue_uinodes.in_set(RenderSet::Queue)) + .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)); // Render graph let ui_graph_2d = get_ui_graph(render_app); diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 560cffa2568e5..001a704a3fa4b 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -21,7 +21,7 @@ use bevy::{ }, texture::BevyDefault, view::{ExtractedView, ViewTarget, VisibleEntities}, - Extract, RenderApp, RenderStage, + Extract, RenderApp, RenderSet, }, sprite::{ DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dUniform, @@ -282,8 +282,8 @@ impl Plugin for ColoredMesh2dPlugin { .add_render_command::() .init_resource::() .init_resource::>() - .add_system_to_stage(RenderStage::Extract, extract_colored_mesh2d) - .add_system_to_stage(RenderStage::Queue, queue_colored_mesh2d); + .add_system_to_stage(RenderSet::Extract, extract_colored_mesh2d) + .add_system_to_stage(RenderSet::Queue, queue_colored_mesh2d); } } diff --git a/examples/shader/compute_shader_game_of_life.rs b/examples/shader/compute_shader_game_of_life.rs index 74b5cba036a04..f64b7e25250f4 100644 --- a/examples/shader/compute_shader_game_of_life.rs +++ b/examples/shader/compute_shader_game_of_life.rs @@ -11,7 +11,7 @@ use bevy::{ render_graph::{self, RenderGraph}, render_resource::*, renderer::{RenderContext, RenderDevice}, - RenderApp, RenderStage, + RenderApp, RenderSet, }, }; use std::borrow::Cow; @@ -73,7 +73,7 @@ impl Plugin for GameOfLifeComputePlugin { let render_app = app.sub_app_mut(RenderApp); render_app .init_resource::() - .add_system_to_stage(RenderStage::Queue, queue_bind_group); + .add_system_to_stage(RenderSet::Queue, queue_bind_group); let mut render_graph = render_app.world.resource_mut::(); render_graph.add_node("game_of_life", GameOfLifeNode::default()); diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index 711f0d2293c8c..652c75bb14ac8 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -19,7 +19,7 @@ use bevy::{ render_resource::*, renderer::RenderDevice, view::{ExtractedView, NoFrustumCulling}, - RenderApp, RenderStage, + RenderApp, RenderSet, }, }; use bytemuck::{Pod, Zeroable}; @@ -85,8 +85,8 @@ impl Plugin for CustomMaterialPlugin { .add_render_command::() .init_resource::() .init_resource::>() - .add_system_to_stage(RenderStage::Queue, queue_custom) - .add_system_to_stage(RenderStage::Prepare, prepare_instance_buffers); + .add_system_to_stage(RenderSet::Queue, queue_custom) + .add_system_to_stage(RenderSet::Prepare, prepare_instance_buffers); } } diff --git a/examples/stress_tests/many_lights.rs b/examples/stress_tests/many_lights.rs index 19324a7ff836b..593e1b2dd5e1a 100644 --- a/examples/stress_tests/many_lights.rs +++ b/examples/stress_tests/many_lights.rs @@ -8,7 +8,7 @@ use bevy::{ math::{DVec2, DVec3}, pbr::{ExtractedPointLight, GlobalLightMeta}, prelude::*, - render::{camera::ScalingMode, Extract, RenderApp, RenderStage}, + render::{camera::ScalingMode, Extract, RenderApp, RenderSet}, window::PresentMode, }; use rand::{thread_rng, Rng}; @@ -158,8 +158,8 @@ impl Plugin for LogVisibleLights { }; render_app - .add_system_to_stage(RenderStage::Extract, extract_time) - .add_system_to_stage(RenderStage::Prepare, print_visible_light_count); + .add_system_to_stage(RenderSet::Extract, extract_time) + .add_system_to_stage(RenderSet::Prepare, print_visible_light_count); } } From e4e64148b76feb33ef06377cb5525d18cebccec8 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 16:36:01 -0500 Subject: [PATCH 035/247] SystemLabel -> SystemSet --- .../benches/bevy_ecs/scheduling/schedule.rs | 4 ++-- crates/bevy_app/src/lib.rs | 6 +++--- crates/bevy_asset/src/asset_server.rs | 2 +- crates/bevy_asset/src/debug_asset_server.rs | 4 ++-- crates/bevy_asset/src/lib.rs | 4 ++-- crates/bevy_ecs/examples/derive_label.rs | 2 +- crates/bevy_ecs/macros/src/lib.rs | 15 --------------- crates/bevy_ecs/src/schedule/label.rs | 15 --------------- .../src/system/exclusive_function_system.rs | 10 +++++----- crates/bevy_ecs/src/system/function_system.rs | 18 +++++++++--------- crates/bevy_ecs/src/system/mod.rs | 4 ++-- crates/bevy_input/src/lib.rs | 4 ++-- crates/bevy_pbr/src/light.rs | 2 +- crates/bevy_pbr/src/render/light.rs | 2 +- crates/bevy_render/src/camera/projection.rs | 2 +- crates/bevy_render/src/lib.rs | 2 +- crates/bevy_render/src/render_asset.rs | 2 +- crates/bevy_render/src/view/visibility/mod.rs | 2 +- crates/bevy_render/src/view/window.rs | 2 +- crates/bevy_sprite/src/lib.rs | 4 ++-- crates/bevy_time/src/lib.rs | 2 +- crates/bevy_transform/src/lib.rs | 2 +- crates/bevy_ui/src/lib.rs | 4 ++-- crates/bevy_ui/src/render/mod.rs | 2 +- crates/bevy_window/src/lib.rs | 4 ++-- 25 files changed, 45 insertions(+), 75 deletions(-) delete mode 100644 crates/bevy_ecs/src/schedule/label.rs diff --git a/benches/benches/bevy_ecs/scheduling/schedule.rs b/benches/benches/bevy_ecs/scheduling/schedule.rs index 524b3aeda8077..c6a41e25ce420 100644 --- a/benches/benches/bevy_ecs/scheduling/schedule.rs +++ b/benches/benches/bevy_ecs/scheduling/schedule.rs @@ -65,10 +65,10 @@ pub fn build_schedule(criterion: &mut Criterion) { // doesn't somehow get optimized away. #[derive(Debug, Clone, Copy)] struct NumLabel(usize); - #[derive(Debug, Clone, Copy, SystemLabel)] + #[derive(Debug, Clone, Copy, SystemSet)] struct DummyLabel; - impl SystemLabel for NumLabel { + impl SystemSet for NumLabel { fn as_str(&self) -> &'static str { let s = self.0.to_string(); Box::leak(s.into_boxed_str()) diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index c5f51e9f6da57..6af4b1c8b6d8b 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -25,7 +25,7 @@ pub mod prelude { pub use crate::{app::App, CoreSet, DynamicPlugin, Plugin, PluginGroup, StartupSet}; } -use bevy_ecs::schedule::{ScheduleLabel, SystemLabel}; +use bevy_ecs::schedule::{ScheduleLabel, SystemSet}; /// The names of the default [`App`] schedules. /// @@ -41,7 +41,7 @@ pub enum CoreSchedule { /// The names of the default [`App`] system sets. /// /// The corresponding [`SystemSets`](bevy_ecs::schedule::SystemSet) are added by [`App::add_default_sets`]. -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum CoreSet { /// Runs before all other app stages. First, @@ -58,7 +58,7 @@ pub enum CoreSet { /// The names of the default [`App`] startup stages. /// /// The corresponding [`SystemSets`](bevy_ecs::schedule::SystemSet) are added by [`App::add_default_sets`]. -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum StartupSet { /// Runs once before [`StartupSet::Startup`]. PreStartup, diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index a4f6b5796edf9..00866212e8163 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -847,7 +847,7 @@ mod test { asset_server.add_loader(FakePngLoader); let assets = asset_server.register_asset_type::(); - #[derive(SystemLabel, Clone, Hash, Debug, PartialEq, Eq)] + #[derive(SystemSet, Clone, Hash, Debug, PartialEq, Eq)] struct FreeUnusedAssets; let mut app = App::new(); app.insert_resource(assets); diff --git a/crates/bevy_asset/src/debug_asset_server.rs b/crates/bevy_asset/src/debug_asset_server.rs index e8809d5269c58..0859b47585106 100644 --- a/crates/bevy_asset/src/debug_asset_server.rs +++ b/crates/bevy_asset/src/debug_asset_server.rs @@ -5,7 +5,7 @@ use bevy_app::{App, Plugin}; use bevy_ecs::{ event::Events, - schedule::SystemLabel, + schedule::SystemSet, system::{NonSendMut, Res, ResMut, Resource, SystemState}, }; use bevy_tasks::{IoTaskPool, TaskPoolBuilder}; @@ -37,7 +37,7 @@ impl DerefMut for DebugAssetApp { } /// A label describing the system that runs [`DebugAssetApp`]. -#[derive(SystemLabel, Debug, Clone, PartialEq, Eq, Hash)] +#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] pub struct DebugAssetAppRun; /// Facilitates the creation of a "debug asset app", whose sole responsibility is hot reloading diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index ab48d405a4eb5..2af7512f99359 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -47,10 +47,10 @@ pub use path::*; pub use reflect::*; use bevy_app::{prelude::Plugin, App}; -use bevy_ecs::schedule::SystemLabel; +use bevy_ecs::schedule::SystemSet; /// The names of asset stages in an [`App`] schedule. -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum AssetSet { /// The stage where asset storages are updated. LoadAssets, diff --git a/crates/bevy_ecs/examples/derive_label.rs b/crates/bevy_ecs/examples/derive_label.rs index 573b42dc8153d..c10eba3e89e84 100644 --- a/crates/bevy_ecs/examples/derive_label.rs +++ b/crates/bevy_ecs/examples/derive_label.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use bevy_ecs::prelude::*; +use bevy_ecs::schedule::SystemLabel; fn main() { // Unit labels are always equal. diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 6259799f3359d..9b6cf92c7d1cb 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -524,21 +524,6 @@ pub fn derive_world_query(input: TokenStream) -> TokenStream { derive_world_query_impl(ast) } -/// Generates an impl of the `SystemLabel` trait. -/// -/// This works only for unit structs, or enums with only unit variants. -/// You may force a struct or variant to behave as if it were fieldless with `#[system_label(ignore_fields)]`. -#[proc_macro_derive(SystemLabel, attributes(system_label))] -pub fn derive_system_label(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let mut trait_path = bevy_ecs_path(); - trait_path.segments.push(format_ident!("schedule").into()); - trait_path - .segments - .push(format_ident!("SystemLabel").into()); - derive_label(input, &trait_path, "system_label") -} - /// Generates an impl of the `RunCriteriaLabel` trait. /// /// This works only for unit structs, or enums with only unit variants. diff --git a/crates/bevy_ecs/src/schedule/label.rs b/crates/bevy_ecs/src/schedule/label.rs deleted file mode 100644 index 8ccb6ad8b4975..0000000000000 --- a/crates/bevy_ecs/src/schedule/label.rs +++ /dev/null @@ -1,15 +0,0 @@ -pub use bevy_ecs_macros::{RunCriteriaLabel, SystemLabel}; -use bevy_utils::define_label; - -define_label!( - /// A strongly-typed class of labels used to identify [`System`](crate::system::System)s. - SystemLabel, - /// Strongly-typed identifier for a [`SystemLabel`]. - SystemLabelId, -); -define_label!( - /// A strongly-typed class of labels used to identify [run criteria](crate::schedule::RunCriteria). - RunCriteriaLabel, - /// Strongly-typed identifier for a [`RunCriteriaLabel`]. - RunCriteriaLabelId, -); diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index eebda6b876eb9..604754cb52727 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -3,10 +3,10 @@ use crate::{ change_detection::MAX_CHANGE_AGE, component::ComponentId, query::Access, - schedule::{SystemLabel, SystemLabelId}, + schedule::{SystemSet, SystemSetId}, system::{ - check_system_change_tick, AsSystemLabel, ExclusiveSystemParam, ExclusiveSystemParamItem, - In, InputMarker, IntoSystem, System, SystemMeta, SystemTypeIdLabel, + check_system_change_tick, AsSystemSet, ExclusiveSystemParam, ExclusiveSystemParamItem, In, + InputMarker, IntoSystem, System, SystemMeta, SystemTypeIdLabel, }, world::{World, WorldId}, }; @@ -161,14 +161,14 @@ where } } -impl AsSystemLabel<(In, Out, Param, Marker, IsExclusiveFunctionSystem)> +impl AsSystemSet<(In, Out, Param, Marker, IsExclusiveFunctionSystem)> for T where Param: ExclusiveSystemParam, T: ExclusiveSystemParamFunction, { #[inline] - fn as_system_label(&self) -> SystemLabelId { + fn as_system_label(&self) -> SystemSetId { SystemTypeIdLabel::(PhantomData).as_label() } } diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 300f29eeca1e1..2098804903dcb 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -4,7 +4,7 @@ use crate::{ component::ComponentId, prelude::FromWorld, query::{Access, FilteredAccessSet}, - schedule::{SystemLabel, SystemLabelId}, + schedule::{SystemSet, SystemSetId}, system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem}, world::{World, WorldId}, }; @@ -529,10 +529,10 @@ where } } -/// A [`SystemLabel`] that was automatically generated for a system on the basis of its `TypeId`. +/// A [`SystemSet`] that was automatically generated for a system on the basis of its `TypeId`. pub struct SystemTypeIdLabel(pub(crate) PhantomData T>); -impl SystemLabel for SystemTypeIdLabel { +impl SystemSet for SystemTypeIdLabel { #[inline] fn as_str(&self) -> &'static str { std::any::type_name::() @@ -679,22 +679,22 @@ all_tuples!(impl_system_function, 0, 16, F); /// Used to implicitly convert systems to their default labels. For example, it will convert /// "system functions" to their [`SystemTypeIdLabel`]. -pub trait AsSystemLabel { - fn as_system_label(&self) -> SystemLabelId; +pub trait AsSystemSet { + fn as_system_label(&self) -> SystemSetId; } impl> - AsSystemLabel<(In, Out, Param, Marker)> for T + AsSystemSet<(In, Out, Param, Marker)> for T { #[inline] - fn as_system_label(&self) -> SystemLabelId { + fn as_system_label(&self) -> SystemSetId { SystemTypeIdLabel::(PhantomData).as_label() } } -impl AsSystemLabel<()> for T { +impl AsSystemSet<()> for T { #[inline] - fn as_system_label(&self) -> SystemLabelId { + fn as_system_label(&self) -> SystemSetId { self.as_label() } } diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 67d4bde3a2fbd..38dd31a4ed8a3 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -42,10 +42,10 @@ //! - **System Stages:** They determine hard execution synchronization boundaries inside of //! which systems run in parallel by default. //! - **Labels:** Systems may be ordered within a stage using the methods `.before()` and `.after()`, -//! which order systems based on their [`SystemLabel`]s. Each system is implicitly labeled with +//! which order systems based on their [`SystemSet`]s. Each system is implicitly labeled with //! its `fn` type, and custom labels may be added by calling `.in_set()`. //! -//! [`SystemLabel`]: crate::schedule::SystemLabel +//! [`SystemSet`]: crate::schedule::SystemSet //! //! ## Example //! diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index 7f73a13a6b347..4afa9a998460c 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -22,7 +22,7 @@ pub mod prelude { } use bevy_app::prelude::*; -use bevy_ecs::schedule::{IntoSystemDescriptor, SystemLabel, SystemSet}; +use bevy_ecs::schedule::{IntoSystemDescriptor, SystemSet, SystemSet}; use bevy_reflect::{FromReflect, Reflect}; use keyboard::{keyboard_input_system, KeyCode, KeyboardInput, ScanCode}; use mouse::{ @@ -46,7 +46,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; #[derive(Default)] pub struct InputPlugin; -#[derive(Debug, PartialEq, Eq, Clone, Hash, SystemLabel)] +#[derive(Debug, PartialEq, Eq, Clone, Hash, SystemSet)] pub struct InputSystem; impl Plugin for InputPlugin { diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index abad3c9b395d8..e7c6c2bd9a8ae 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -289,7 +289,7 @@ pub struct NotShadowCaster; #[reflect(Component, Default)] pub struct NotShadowReceiver; -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum SimulationLightSystems { AddClusters, AssignLightsToClusters, diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 657534c30c220..1735c390f3038 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -38,7 +38,7 @@ use bevy_utils::{ }; use std::num::{NonZeroU32, NonZeroU64}; -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum RenderLightSystems { ExtractClusters, ExtractLights, diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index 51fe266738f79..cb67414b8632e 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -22,7 +22,7 @@ impl Default for CameraProjectionPlugin { /// Label for [`camera_system`], shared across all `T`. /// /// [`camera_system`]: crate::camera::camera_system -#[derive(SystemLabel, Clone, Eq, PartialEq, Hash, Debug)] +#[derive(SystemSet, Clone, Eq, PartialEq, Hash, Debug)] pub struct CameraUpdateSystem; impl Plugin for CameraProjectionPlugin { diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 434f7d0b77fc9..330da4ac2130f 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -64,7 +64,7 @@ pub struct RenderPlugin { } /// The labels of the default App rendering stages. -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum RenderSet { /// Extract data from the "app world" and insert it into the "render world". /// This step should be kept as short as possible to increase the "pipelining potential" for diff --git a/crates/bevy_render/src/render_asset.rs b/crates/bevy_render/src/render_asset.rs index 1118929942f70..2fd372e5a6acb 100644 --- a/crates/bevy_render/src/render_asset.rs +++ b/crates/bevy_render/src/render_asset.rs @@ -40,7 +40,7 @@ pub trait RenderAsset: Asset { ) -> Result>; } -#[derive(Clone, Hash, Debug, Default, PartialEq, Eq, SystemLabel)] +#[derive(Clone, Hash, Debug, Default, PartialEq, Eq, SystemSet)] pub enum PrepareAssetLabel { PreAssetPrepare, #[default] diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 6f39d75c21996..7738011a3ae9d 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -192,7 +192,7 @@ impl VisibleEntities { } } -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum VisibilitySystems { CalculateBounds, UpdateOrthographicFrusta, diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index e5ff0a91a9067..9fa35043cc107 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -18,7 +18,7 @@ pub struct NonSendMarker; pub struct WindowRenderPlugin; -#[derive(SystemLabel, Debug, Clone, PartialEq, Eq, Hash)] +#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] pub enum WindowSystem { Prepare, } diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 019d6a90b9823..a79a88789cdb3 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -29,7 +29,7 @@ pub use texture_atlas_builder::*; use bevy_app::prelude::*; use bevy_asset::{AddAsset, Assets, HandleUntyped}; use bevy_core_pipeline::core_2d::Transparent2d; -use bevy_ecs::schedule::{IntoSystemDescriptor, SystemLabel}; +use bevy_ecs::schedule::{IntoSystemDescriptor, SystemSet}; use bevy_reflect::TypeUuid; use bevy_render::{ render_phase::AddRenderCommand, @@ -43,7 +43,7 @@ pub struct SpritePlugin; pub const SPRITE_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 2763343953151597127); -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum SpriteSystem { ExtractSprites, } diff --git a/crates/bevy_time/src/lib.rs b/crates/bevy_time/src/lib.rs index 88bb3db474f61..68324ada2ed4c 100644 --- a/crates/bevy_time/src/lib.rs +++ b/crates/bevy_time/src/lib.rs @@ -26,7 +26,7 @@ use bevy_ecs::prelude::*; #[derive(Default)] pub struct TimePlugin; -#[derive(Debug, PartialEq, Eq, Clone, Hash, SystemLabel)] +#[derive(Debug, PartialEq, Eq, Clone, Hash, SystemSet)] /// Updates the elapsed time. Any system that interacts with [Time] component should run after /// this. pub struct TimeSystem; diff --git a/crates/bevy_transform/src/lib.rs b/crates/bevy_transform/src/lib.rs index 50aa14089aed4..8b561b2c7bcca 100644 --- a/crates/bevy_transform/src/lib.rs +++ b/crates/bevy_transform/src/lib.rs @@ -76,7 +76,7 @@ impl From for TransformBundle { } } /// Label enum for the systems relating to transform propagation -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum TransformSystem { /// Propagates changes in transform to children's [`GlobalTransform`](crate::components::GlobalTransform) TransformPropagate, diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index f54d39bfb55a3..283c16fb101fa 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -32,7 +32,7 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_ecs::{ - schedule::{IntoSystemDescriptor, SystemLabel}, + schedule::{IntoSystemDescriptor, SystemSet}, system::Resource, }; use bevy_input::InputSystem; @@ -49,7 +49,7 @@ use crate::prelude::UiCameraConfig; pub struct UiPlugin; /// The label enum labeling the types of systems in the Bevy UI -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum UiSystem { /// After this label, the ui flex state has been updated Flex, diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index b55e83a9c2d87..2d23746df43c2 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -50,7 +50,7 @@ pub mod draw_ui_graph { pub const UI_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 13012847047162779583); -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum RenderUiSystem { ExtractNode, } diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index 1f74d3a78ddc1..d00d6107bf16b 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -23,7 +23,7 @@ pub mod prelude { } use bevy_app::prelude::*; -use bevy_ecs::schedule::{IntoSystemDescriptor, SystemLabel}; +use bevy_ecs::schedule::{IntoSystemDescriptor, SystemSet}; use std::path::PathBuf; impl Default for WindowPlugin { @@ -131,5 +131,5 @@ impl Plugin for WindowPlugin { } } -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub struct ModifiesWindows; From eeef0289a7b78376f5e47dfa9a747f042334e494 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 16:39:29 -0500 Subject: [PATCH 036/247] Remove RunCriteriaLabel --- crates/bevy_ecs/macros/src/lib.rs | 19 +------------------ crates/bevy_ecs/src/schedule/mod.rs | 2 -- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 9b6cf92c7d1cb..f553c8ef7d2b0 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -4,9 +4,7 @@ mod component; mod fetch; use crate::fetch::derive_world_query_impl; -use bevy_macro_utils::{ - derive_boxed_label, derive_label, derive_set, get_named_struct_fields, BevyManifest, -}; +use bevy_macro_utils::{derive_boxed_label, derive_set, get_named_struct_fields, BevyManifest}; use proc_macro::TokenStream; use proc_macro2::Span; use quote::{format_ident, quote}; @@ -524,21 +522,6 @@ pub fn derive_world_query(input: TokenStream) -> TokenStream { derive_world_query_impl(ast) } -/// Generates an impl of the `RunCriteriaLabel` trait. -/// -/// This works only for unit structs, or enums with only unit variants. -/// You may force a struct or variant to behave as if it were fieldless with `#[run_criteria_label(ignore_fields)]`. -#[proc_macro_derive(RunCriteriaLabel, attributes(run_criteria_label))] -pub fn derive_run_criteria_label(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let mut trait_path = bevy_ecs_path(); - trait_path.segments.push(format_ident!("schedule").into()); - trait_path - .segments - .push(format_ident!("RunCriteriaLabel").into()); - derive_label(input, &trait_path, "run_criteria_label") -} - /// Derive macro generating an impl of the trait `ScheduleLabel`. #[proc_macro_derive(ScheduleLabel)] pub fn derive_schedule_label(input: TokenStream) -> TokenStream { diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index 6f81da9999c66..4548b78dc51e4 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -2,7 +2,6 @@ mod condition; mod config; mod executor; mod graph_utils; -mod label; mod schedule; mod set; mod state; @@ -11,7 +10,6 @@ pub use self::condition::*; pub use self::config::*; pub use self::executor::*; use self::graph_utils::*; -pub use self::label::*; pub use self::schedule::*; pub use self::set::*; pub use self::state::*; From ca3f0b70ed84e27c51f66d2901dd584e44a37fa6 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 16:50:05 -0500 Subject: [PATCH 037/247] Remove vestigal SystemSet code --- crates/bevy_app/src/app.rs | 18 +++++-------- .../src/system/exclusive_function_system.rs | 3 +-- crates/bevy_ecs/src/system/function_system.rs | 27 +------------------ crates/bevy_input/src/lib.rs | 13 ++++----- crates/bevy_transform/src/lib.rs | 9 ++++--- 5 files changed, 21 insertions(+), 49 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index bb97d0ce60fb2..556445a2c4a82 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -326,12 +326,7 @@ impl App { /// # fn system_b() {} /// # fn system_c() {} /// # - /// app.add_system_set( - /// SystemSet::new() - /// .with_system(system_a) - /// .with_system(system_b) - /// .with_system(system_c), - /// ); + /// app.add_systems((system_a, system_b, system_c)); /// ``` pub fn add_systems

(&mut self, systems: impl IntoSystemConfigs

) -> &mut Self { let schedules = self.world.resource_mut::(); @@ -418,11 +413,12 @@ impl App { /// # fn startup_system_b() {} /// # fn startup_system_c() {} /// # - /// app.add_startup_system_set( - /// SystemSet::new() - /// .with_system(startup_system_a) - /// .with_system(startup_system_b) - /// .with_system(startup_system_c), + /// app.add_startup_systems( + /// ( + /// startup_system_a, + /// startup_system_b, + /// startup_system_c, + /// ) /// ); /// ``` pub fn add_startup_systems

(&mut self, systems: impl IntoSystemConfigs

) -> &mut Self { diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index 604754cb52727..74d365b817c68 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -3,10 +3,9 @@ use crate::{ change_detection::MAX_CHANGE_AGE, component::ComponentId, query::Access, - schedule::{SystemSet, SystemSetId}, system::{ check_system_change_tick, AsSystemSet, ExclusiveSystemParam, ExclusiveSystemParamItem, In, - InputMarker, IntoSystem, System, SystemMeta, SystemTypeIdLabel, + InputMarker, IntoSystem, System, SystemMeta, }, world::{World, WorldId}, }; diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 2098804903dcb..14417f4be96a1 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -4,7 +4,7 @@ use crate::{ component::ComponentId, prelude::FromWorld, query::{Access, FilteredAccessSet}, - schedule::{SystemSet, SystemSetId}, + schedule::SystemSet, system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem}, world::{World, WorldId}, }; @@ -529,31 +529,6 @@ where } } -/// A [`SystemSet`] that was automatically generated for a system on the basis of its `TypeId`. -pub struct SystemTypeIdLabel(pub(crate) PhantomData T>); - -impl SystemSet for SystemTypeIdLabel { - #[inline] - fn as_str(&self) -> &'static str { - std::any::type_name::() - } -} - -impl Debug for SystemTypeIdLabel { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("SystemTypeIdLabel") - .field(&std::any::type_name::()) - .finish() - } -} - -impl Clone for SystemTypeIdLabel { - fn clone(&self) -> Self { - *self - } -} -impl Copy for SystemTypeIdLabel {} - /// A trait implemented for all functions that can be used as [`System`]s. /// /// This trait can be useful for making your own systems which accept other systems, diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index 4afa9a998460c..a7e6383406479 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -81,12 +81,13 @@ impl Plugin for InputPlugin { .init_resource::>() .init_resource::>() .init_resource::>() - .add_system_set( - SystemSet::new() - .with_system(gamepad_event_system) - .with_system(gamepad_button_event_system.after(gamepad_event_system)) - .with_system(gamepad_axis_event_system.after(gamepad_event_system)) - .with_system(gamepad_connection_system.after(gamepad_event_system)) + .add_systems( + ( + gamepad_event_system, + gamepad_button_event_system.after(gamepad_event_system), + gamepad_axis_event_system.after(gamepad_event_system), + gamepad_connection_system.after(gamepad_event_system), + ) .in_set(InputSystem) .in_set(CoreSet::PreUpdate), ) diff --git a/crates/bevy_transform/src/lib.rs b/crates/bevy_transform/src/lib.rs index 8b561b2c7bcca..231a1ae631f75 100644 --- a/crates/bevy_transform/src/lib.rs +++ b/crates/bevy_transform/src/lib.rs @@ -83,10 +83,11 @@ pub enum TransformSystem { } /// Transform propagation system set for third party plugins use -pub fn transform_propagate_system_set() -> SystemSet { - SystemSet::new() - .with_system(systems::sync_simple_transforms) - .with_system(systems::propagate_transforms) +pub fn transform_propagate_system_set() -> impl IntoSystemConfigs

{ + ( + systems::sync_simple_transforms, + systems::propagate_transforms, + ) } /// The base plugin for handling [`Transform`] components From 5831cf8bdd53e0082a36250732e0c0097f7a4452 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Jan 2023 16:51:31 -0500 Subject: [PATCH 038/247] Remove AsSystemLabel trait --- .../src/system/exclusive_function_system.rs | 16 ++---------- crates/bevy_ecs/src/system/function_system.rs | 25 +------------------ 2 files changed, 3 insertions(+), 38 deletions(-) diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index 74d365b817c68..3aefbc868e090 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -4,8 +4,8 @@ use crate::{ component::ComponentId, query::Access, system::{ - check_system_change_tick, AsSystemSet, ExclusiveSystemParam, ExclusiveSystemParamItem, In, - InputMarker, IntoSystem, System, SystemMeta, + check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, In, InputMarker, + IntoSystem, System, SystemMeta, }, world::{World, WorldId}, }; @@ -160,18 +160,6 @@ where } } -impl AsSystemSet<(In, Out, Param, Marker, IsExclusiveFunctionSystem)> - for T -where - Param: ExclusiveSystemParam, - T: ExclusiveSystemParamFunction, -{ - #[inline] - fn as_system_label(&self) -> SystemSetId { - SystemTypeIdLabel::(PhantomData).as_label() - } -} - /// A trait implemented for all exclusive system functions that can be used as [`System`]s. /// /// This trait can be useful for making your own systems which accept other systems, diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 14417f4be96a1..b1e4c73e86332 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -4,12 +4,11 @@ use crate::{ component::ComponentId, prelude::FromWorld, query::{Access, FilteredAccessSet}, - schedule::SystemSet, system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem}, world::{World, WorldId}, }; use bevy_ecs_macros::all_tuples; -use std::{any::TypeId, borrow::Cow, fmt::Debug, marker::PhantomData}; +use std::{any::TypeId, borrow::Cow, marker::PhantomData}; /// The metadata of a [`System`]. #[derive(Clone)] @@ -651,25 +650,3 @@ macro_rules! impl_system_function { // Note that we rely on the highest impl to be <= the highest order of the tuple impls // of `SystemParam` created. all_tuples!(impl_system_function, 0, 16, F); - -/// Used to implicitly convert systems to their default labels. For example, it will convert -/// "system functions" to their [`SystemTypeIdLabel`]. -pub trait AsSystemSet { - fn as_system_label(&self) -> SystemSetId; -} - -impl> - AsSystemSet<(In, Out, Param, Marker)> for T -{ - #[inline] - fn as_system_label(&self) -> SystemSetId { - SystemTypeIdLabel::(PhantomData).as_label() - } -} - -impl AsSystemSet<()> for T { - #[inline] - fn as_system_label(&self) -> SystemSetId { - self.as_label() - } -} From 6b26e93d31969bfe6142dea055aaea27f77ff5da Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 09:32:41 -0500 Subject: [PATCH 039/247] Migrate changed_resource_system test --- crates/bevy_ecs/src/system/mod.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 38dd31a4ed8a3..25bd22f2998a2 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -139,7 +139,7 @@ mod tests { entity::{Entities, Entity}, prelude::AnyOf, query::{Added, Changed, Or, With, Without}, - schedule::Schedule, + schedule::{apply_system_buffers, IntoSystemConfig, Schedule}, system::{ Commands, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, QueryComponentError, RemovedComponents, Res, ResMut, Resource, System, SystemState, @@ -309,14 +309,11 @@ mod tests { world.insert_resource(Added(0)); world.insert_resource(Changed(0)); - #[derive(StageLabel)] - struct ClearTrackers; - let mut schedule = Schedule::default(); - let mut update = SystemStage::parallel(); - update.add_system(incr_e_on_flip); - schedule.add_stage(UpdateStage, update); - schedule.add_stage(ClearTrackers, SystemStage::single(World::clear_trackers)); + + schedule.add_system(incr_e_on_flip); + schedule.add_system(apply_system_buffers.after(incr_e_on_flip)); + schedule.add_system(World::clear_trackers.after(apply_system_buffers)); schedule.run(&mut world); assert_eq!(world.resource::().0, 1); From b91ea90c7cddf04f9662b3d38edc8b91dd9bf6d0 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 09:38:34 -0500 Subject: [PATCH 040/247] Remove obsolete bevy_ecs derive_label example --- crates/bevy_ecs/examples/derive_label.rs | 62 ------------------------ 1 file changed, 62 deletions(-) delete mode 100644 crates/bevy_ecs/examples/derive_label.rs diff --git a/crates/bevy_ecs/examples/derive_label.rs b/crates/bevy_ecs/examples/derive_label.rs deleted file mode 100644 index c10eba3e89e84..0000000000000 --- a/crates/bevy_ecs/examples/derive_label.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::marker::PhantomData; - -use bevy_ecs::schedule::SystemLabel; - -fn main() { - // Unit labels are always equal. - assert_eq!(UnitLabel.as_label(), UnitLabel.as_label()); - - // Enum labels depend on the variant. - assert_eq!(EnumLabel::One.as_label(), EnumLabel::One.as_label()); - assert_ne!(EnumLabel::One.as_label(), EnumLabel::Two.as_label()); - - // Labels annotated with `ignore_fields` ignore their fields. - assert_eq!(WeirdLabel(1).as_label(), WeirdLabel(2).as_label()); - - // Labels don't depend only on the variant name but on the full type - assert_ne!( - GenericLabel::::One.as_label(), - GenericLabel::::One.as_label(), - ); -} - -#[derive(SystemLabel)] -pub struct UnitLabel; - -#[derive(SystemLabel)] -pub enum EnumLabel { - One, - Two, -} - -#[derive(SystemLabel)] -#[system_label(ignore_fields)] -pub struct WeirdLabel(i32); - -#[derive(SystemLabel)] -pub enum GenericLabel { - One, - #[system_label(ignore_fields)] - Two(PhantomData), -} - -// FIXME: this should be a compile_fail test -/*#[derive(SystemLabel)] -pub union Foo { - x: i32, -}*/ - -// FIXME: this should be a compile_fail test -/*#[derive(SystemLabel)] -#[system_label(ignore_fields)] -pub enum BadLabel { - One, - Two, -}*/ - -// FIXME: this should be a compile_fail test -/*#[derive(SystemLabel)] -pub struct BadLabel2 { - #[system_label(ignore_fields)] - x: (), -}*/ From 42c2949be52eafe874adabad213a5f7d4ae2e4d1 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 09:41:02 -0500 Subject: [PATCH 041/247] Fix bevy_ecs change_detection.rs example --- crates/bevy_ecs/examples/change_detection.rs | 25 +++++++------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/crates/bevy_ecs/examples/change_detection.rs b/crates/bevy_ecs/examples/change_detection.rs index 0578c601f09e3..3ed5a5dcacb8f 100644 --- a/crates/bevy_ecs/examples/change_detection.rs +++ b/crates/bevy_ecs/examples/change_detection.rs @@ -1,4 +1,4 @@ -use bevy_ecs::prelude::*; +use bevy_ecs::{prelude::*, schedule::IntoSystemConfig}; use rand::Rng; use std::ops::Deref; @@ -16,23 +16,16 @@ fn main() { // Add the counter resource to remember how many entities where spawned world.insert_resource(EntityCounter { value: 0 }); - // Create a new Schedule, which defines an execution strategy for Systems + // Create a new Schedule, which stores systems and controls their relative ordering let mut schedule = Schedule::default(); - // Create a Stage to add to our Schedule. Each Stage in a schedule runs all of its systems - // before moving on to the next Stage - let mut update = SystemStage::parallel(); // Add systems to the Stage to execute our app logic // We can label our systems to force a specific run-order between some of them - update.add_system(spawn_entities.label(SimulationSystem::Spawn)); - update.add_system(print_counter_when_changed.after(SimulationSystem::Spawn)); - update.add_system(age_all_entities.label(SimulationSystem::Age)); - update.add_system(remove_old_entities.after(SimulationSystem::Age)); - update.add_system(print_changed_entities.after(SimulationSystem::Age)); - // Add the Stage with our systems to the Schedule - #[derive(StageLabel)] - struct Update; - schedule.add_stage(Update, update); + schedule.add_system(spawn_entities.in_set(SimulationSystem::Spawn)); + schedule.add_system(print_counter_when_changed.after(SimulationSystem::Spawn)); + schedule.add_system(age_all_entities.in_set(SimulationSystem::Age)); + schedule.add_system(remove_old_entities.after(SimulationSystem::Age)); + schedule.add_system(print_changed_entities.after(SimulationSystem::Age)); // Simulate 10 frames in our world for iteration in 1..=10 { @@ -53,8 +46,8 @@ struct Age { frames: i32, } -// System labels to enforce a run order of our systems -#[derive(SystemLabel, Debug, Clone, PartialEq, Eq, Hash)] +// System sets can be used to group systems and configured to control relative ordering +#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] enum SimulationSystem { Spawn, Age, From d454edc23ae392aca5c28bcfeea6baf1ead0d55d Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 09:46:41 -0500 Subject: [PATCH 042/247] Add new schedule types to the prelude --- crates/bevy_ecs/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index e3a85e30ee2d6..80d03a8acbc36 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -33,7 +33,9 @@ pub mod prelude { entity::Entity, event::{EventReader, EventWriter, Events}, query::{Added, AnyOf, ChangeTrackers, Changed, Or, QueryState, With, Without}, - schedule::{Schedule, State, SystemSet}, + schedule::{ + IntoSystemConfig, IntoSystemConfigs, Schedule, Schedules, State, States, SystemSet, + }, system::{ adapter as system_adapter, adapter::{dbg, error, ignore, info, unwrap, warn}, From 9b18e4dae9624776a22fe750b80c51cfb4e34948 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 09:49:27 -0500 Subject: [PATCH 043/247] Update bevy_ecs example events.rs --- crates/bevy_ecs/examples/events.rs | 31 ++++++++++-------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/crates/bevy_ecs/examples/events.rs b/crates/bevy_ecs/examples/events.rs index b14a209823d60..b0f96e39a1421 100644 --- a/crates/bevy_ecs/examples/events.rs +++ b/crates/bevy_ecs/examples/events.rs @@ -7,31 +7,20 @@ fn main() { let mut world = World::new(); world.insert_resource(Events::::default()); - // Create a schedule and a stage + // Create a schedule to store our systems let mut schedule = Schedule::default(); - #[derive(StageLabel)] - enum Stages { - First, - Second, - } - - // Events need to be updated in every frame. This update should happen before we use - // the events. To guarantee this, we can let the update run in an earlier stage than our logic. - // Here we will use a stage called "first" that will always run it's systems before the Stage - // called "second". In "first" we update the events and in "second" we run our systems - // sending and receiving events. - let mut first = SystemStage::parallel(); - first.add_system(Events::::update_system); - schedule.add_stage(Stages::First, first); + // Events need to be updated in every frame in order to clear our buffers. + // This update should happen before we use the events. + // Here, we use system sets to control the ordering. + #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] + pub struct FlushEvents; - // Add systems sending and receiving events to a "second" Stage - let mut second = SystemStage::parallel(); - second.add_system(sending_system); - second.add_system(receiving_system.after(sending_system)); + schedule.add_system(Events::::update_system.in_set(FlushEvents)); - // Run the "second" Stage after the "first" Stage, so our Events always get updated before we use them - schedule.add_stage_after(Stages::First, Stages::Second, second); + // Add systems sending and receiving events after the events are flushed. + schedule.add_system(sending_system.after(FlushEvents)); + schedule.add_system(receiving_system.after(sending_system)); // Simulate 10 frames of our world for iteration in 1..=10 { From f09b997e064593256776eb29ac202c043b39c8db Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 09:50:33 -0500 Subject: [PATCH 044/247] Migrate bevy_ecs example resources.rs --- crates/bevy_ecs/examples/resources.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/crates/bevy_ecs/examples/resources.rs b/crates/bevy_ecs/examples/resources.rs index d4874e98ff6c8..a3fd26fc290a5 100644 --- a/crates/bevy_ecs/examples/resources.rs +++ b/crates/bevy_ecs/examples/resources.rs @@ -13,18 +13,10 @@ fn main() { // Create a schedule and a stage let mut schedule = Schedule::default(); - let mut update = SystemStage::parallel(); // Add systems to increase the counter and to print out the current value - update.add_system(increase_counter); - update.add_system(print_counter.after(increase_counter)); - - // Declare a unique label for the stage. - #[derive(StageLabel)] - struct Update; - - // Add the stage to the schedule. - schedule.add_stage(Update, update); + schedule.add_system(increase_counter); + schedule.add_system(print_counter.after(increase_counter)); for iteration in 1..=10 { println!("Simulating frame {iteration}/10"); From 768c102664f5e019f48a5fe9fa8aaaf20c671056 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 09:59:00 -0500 Subject: [PATCH 045/247] Configure main schedule correctly --- crates/bevy_app/src/app.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 556445a2c4a82..e23d66bd50ebc 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -535,33 +535,33 @@ impl App { main_schedule.configure_set(CoreSet::Last.after(CoreSet::PostUpdate)); // Flush points - startup_schedule.add_system( + main_schedule.add_system( apply_system_buffers .after(CoreSet::First) .before(CoreSet::PreUpdate), ); - startup_schedule.add_system( + main_schedule.add_system( apply_system_buffers .after(CoreSet::PreUpdate) .before(CoreSet::Update), ); - startup_schedule.add_system( + main_schedule.add_system( apply_system_buffers .after(CoreSet::Update) .before(CoreSet::PostUpdate), ); - startup_schedule.add_system( + main_schedule.add_system( apply_system_buffers .after(CoreSet::PostUpdate) .before(CoreSet::Last), ); - startup_schedule.add_system(apply_system_buffers.after(CoreSet::Last)); + main_schedule.add_system(apply_system_buffers.after(CoreSet::Last)); // Default set - startup_schedule.set_default_set(CoreSet::Update); + main_schedule.set_default_set(CoreSet::Update); self } From e1dafc3e55df287f29139e9c5a0248d4720cc16e Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 11:17:04 -0500 Subject: [PATCH 046/247] Collect and flesh out App scheduling manipulation --- crates/bevy_app/src/app.rs | 171 ++++++++++++++++++++----------------- 1 file changed, 93 insertions(+), 78 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index e23d66bd50ebc..51bc3be4b0984 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -217,65 +217,6 @@ impl App { (runner)(app); } - /// Sets the [`Schedule`] that will be modified by default when you call `App::add_system` - /// and similar methods. - /// - /// **Note:** This will create the schedule if it does not already exist. - pub fn set_default_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self { - let mut schedules = self.world.resource_mut::(); - - schedules.default_schedule_label = Some(Box::new(label)); - if schedules.get(&label).is_none() { - schedules.insert(label, Schedule::new()); - } - - self - } - - /// Gets the label of the [`Schedule`] that will be modified by default when you call `App::add_system` - /// and similar methods. - pub fn default_schedule(&self) -> &Option { - let schedules = self.world.resource::(); - &schedules.default_schedule_label - } - - /// Sets the [`Schedule`] that will be run the next time that [`App::update`] or [`App::run`] is called. - pub fn set_active_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self { - let mut schedules = self.world.resource_mut::(); - - schedules.active_schedule_label = Some(Box::new(label)); - - self - } - - /// Gets the label of the [`Schedule`] that will be modified by default when you call `App::add_system` - /// and similar methods. - pub fn active_schedule(&self) -> &Option { - let schedules = self.world.resource::(); - &schedules.active_schedule_label - } - - /// Applies the function to the [`Schedule`] associated with `label`. - /// - /// **Note:** This will create the schedule if it does not already exist. - pub fn edit_schedule( - &mut self, - label: impl ScheduleLabel, - f: impl FnMut(&mut Schedule), - ) -> &mut Self { - let mut schedules = self.world.resource::(); - - if schedules.get(&label).is_none() { - schedules.insert(label, Schedule::new()); - } - - let schedule = schedules.get_mut(&label).unwrap(); - // Call the function f, passing in the schedule retrieved - f(schedule); - - self - } - /// Adds [`State`] and [`NextState`] resources, [`OnEnter`] and [`OnExit`] schedules /// for each state variant, and an instance of [`apply_state_transition::`] in /// \ so that transitions happen before `Update`. @@ -425,23 +366,6 @@ impl App { self.add_systems_to_schedule(systems, CoreSchedule::Startup) } - /// Adds a new `schedule` to the [`App`] under the provided `label. - /// - /// See [`App::add_schedule`] to pass in a pre-constructed schedule. - pub fn add_schedule(&mut self, label: impl ScheduleLabel, schedule: Schedule) -> &mut Self { - let mut schedules = self.world.resource_mut::(); - schedules.insert(label, schedule); - - self - } - - /// Runs the [`Schedule`] with the provided `label` on the app's [`World`] a single time. - pub fn run_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self { - self.world.run_schedule(label); - - self - } - /// Adds standardized schedules and labels to an [`App`]. /// /// Adding these schedules is necessary to make some core engine features work. @@ -494,7 +418,7 @@ impl App { /// ``` pub fn add_default_sets(&mut self) -> &mut Self { // Adding sets - let startup_schedule = self.schedules.get(CoreSchedule::Startup).unwrap(); + let startup_schedule = self.schedule_mut(CoreSchedule::Startup).unwrap(); startup_schedule.add_set(StartupSet::PreStartup); startup_schedule.add_set(StartupSet::Startup); startup_schedule.add_set(StartupSet::PostStartup); @@ -521,7 +445,7 @@ impl App { startup_schedule.set_default_set(StartupSet::Startup); // Adding sets - let main_schedule = self.schedules.get(CoreSchedule::Startup).unwrap(); + let main_schedule = self.schedule_mut(CoreSchedule::Main).unwrap(); main_schedule.add_set(CoreSet::First); main_schedule.add_set(CoreSet::PreUpdate); main_schedule.add_set(CoreSet::Update); @@ -961,6 +885,97 @@ impl App { } } +/// Methods for working with schedules +impl App { + /// Adds a new `schedule` to the [`App`] under the provided `label. + /// + /// See [`App::add_schedule`] to pass in a pre-constructed schedule. + pub fn add_schedule(&mut self, label: impl ScheduleLabel, schedule: Schedule) -> &mut Self { + let mut schedules = self.world.resource_mut::(); + schedules.insert(label, schedule); + + self + } + + /// Runs the [`Schedule`] with the provided `label` on the app's [`World`] a single time. + pub fn run_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self { + self.world.run_schedule(label); + + self + } + + /// Gets read-only access to the [`Schedule`] with the provided `label` if it exists. + pub fn schedule(&self, label: impl ScheduleLabel) -> Option<&Schedule> { + let schedules = self.world.get_resource::()?; + schedules.get(&label) + } + + /// Gets read-write access to a [`Schedule`] with the provided `label` if it exists. + pub fn schedule_mut(&self, label: impl ScheduleLabel) -> Option<&mut Schedule> { + let schedules = self.world.get_resource::()?; + schedules.get_mut(&label) + } + + /// Sets the [`Schedule`] that will be modified by default when you call `App::add_system` + /// and similar methods. + /// + /// **Note:** This will create the schedule if it does not already exist. + pub fn set_default_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self { + let mut schedules = self.world.resource_mut::(); + + schedules.default_schedule_label = Some(Box::new(label)); + if schedules.get(&label).is_none() { + schedules.insert(label, Schedule::new()); + } + + self + } + + /// Gets the label of the [`Schedule`] that will be modified by default when you call `App::add_system` + /// and similar methods. + pub fn default_schedule(&self) -> &Option { + let schedules = self.world.resource::(); + &schedules.default_schedule_label + } + + /// Sets the [`Schedule`] that will be run the next time that [`App::update`] or [`App::run`] is called. + pub fn set_active_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self { + let mut schedules = self.world.resource_mut::(); + + schedules.active_schedule_label = Some(Box::new(label)); + + self + } + + /// Gets the label of the [`Schedule`] that will be modified by default when you call `App::add_system` + /// and similar methods. + pub fn active_schedule(&self) -> &Option { + let schedules = self.world.resource::(); + &schedules.active_schedule_label + } + + /// Applies the function to the [`Schedule`] associated with `label`. + /// + /// **Note:** This will create the schedule if it does not already exist. + pub fn edit_schedule( + &mut self, + label: impl ScheduleLabel, + f: impl FnMut(&mut Schedule), + ) -> &mut Self { + let mut schedules = self.world.resource::(); + + if schedules.get(&label).is_none() { + schedules.insert(label, Schedule::new()); + } + + let schedule = schedules.get_mut(&label).unwrap(); + // Call the function f, passing in the schedule retrieved + f(schedule); + + self + } +} + fn run_once(mut app: App) { app.update(); } From 6df12dff5db90ae8d55545d0e0fb37c510003b8a Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 11:56:40 -0500 Subject: [PATCH 047/247] Implement add_state --- crates/bevy_app/src/app.rs | 41 ++++++++++++++++++++++++--- crates/bevy_app/src/lib.rs | 4 +++ crates/bevy_ecs/src/schedule/state.rs | 14 +++++---- 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 51bc3be4b0984..63fddf918298a 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -4,8 +4,9 @@ use bevy_ecs::{ event::{Event, Events}, prelude::FromWorld, schedule::{ - apply_system_buffers, BoxedScheduleLabel, IntoSystemConfig, IntoSystemConfigs, Schedule, - ScheduleLabel, Schedules, States, + apply_state_transition, apply_system_buffers, BoxedScheduleLabel, IntoSystemConfig, + IntoSystemConfigs, NextState, OnEnter, OnExit, OnUpdate, Schedule, ScheduleLabel, + Schedules, State, States, }, system::Resource, world::World, @@ -219,8 +220,33 @@ impl App { /// Adds [`State`] and [`NextState`] resources, [`OnEnter`] and [`OnExit`] schedules /// for each state variant, and an instance of [`apply_state_transition::`] in - /// \ so that transitions happen before `Update`. - pub fn add_state(&mut self) -> &mut Self; + /// [`CoreSet::StateTransitions`] so that transitions happen before [`CoreSet::Update`]. + /// + /// This also adds an [`OnUpdate`] system set for each state variant, + /// which run during [`CoreSet::StateTransitions`] after the transitions are applied. + /// These systems sets only run if the [`State`] resource matches their label. + /// + /// Note that you can also apply state tranitions at other points in the schedule + /// by adding the [`apply_state_transition`] system manually. + pub fn add_state(&mut self) -> &mut Self { + self.init_resource::>(); + self.init_resource::>(); + self.add_system(apply_state_transition::.in_set(CoreSet::StateTransitions)); + let main_schedule = self.schedule_mut(CoreSchedule::Main).unwrap(); + + for variant in S::variants() { + self.add_schedule(OnEnter(variant), Schedule::new()); + self.add_schedule(OnExit(variant), Schedule::new()); + main_schedule.configure_set( + OnUpdate(variant) + .in_set(CoreSet::StateTransitions) + .run_if_state_equals(variant) + .after(apply_state_transition::), + ); + } + + self + } /// Adds a system to the default system set and schedule of the app's [`Schedules`]. /// @@ -455,6 +481,7 @@ impl App { // Ordering main_schedule.configure_set(CoreSet::First.before(CoreSet::PreUpdate)); main_schedule.configure_set(CoreSet::PreUpdate.before(CoreSet::Update)); + main_schedule.configure_set(CoreSet::StateTransitions.before(CoreSet::Update)); main_schedule.configure_set(CoreSet::PostUpdate.after(CoreSet::Update)); main_schedule.configure_set(CoreSet::Last.after(CoreSet::PostUpdate)); @@ -467,6 +494,12 @@ impl App { main_schedule.add_system( apply_system_buffers .after(CoreSet::PreUpdate) + .before(CoreSet::StateTransitions), + ); + + main_schedule.add_system( + apply_system_buffers + .after(CoreSet::StateTransitions) .before(CoreSet::Update), ); diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index 6af4b1c8b6d8b..b84fdea1d5973 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -40,6 +40,8 @@ pub enum CoreSchedule { /// The names of the default [`App`] system sets. /// +/// These are ordered in the same order they are listed. +/// /// The corresponding [`SystemSets`](bevy_ecs::schedule::SystemSet) are added by [`App::add_default_sets`]. #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum CoreSet { @@ -47,6 +49,8 @@ pub enum CoreSet { First, /// Runs before [`CoreStage::Update`]. PreUpdate, + /// Applies [`State`](bevy_ecs::schedule::State) transitions + StateTransitions, /// Responsible for doing most app logic. Systems should be registered here by default. Update, /// Runs after [`CoreStage::Update`]. diff --git a/crates/bevy_ecs/src/schedule/state.rs b/crates/bevy_ecs/src/schedule/state.rs index eb1b9e11754e2..11f0e25db02e8 100644 --- a/crates/bevy_ecs/src/schedule/state.rs +++ b/crates/bevy_ecs/src/schedule/state.rs @@ -8,11 +8,13 @@ use crate::system::Resource; use crate::world::World; /// Types that can define states in a finite-state machine. -pub trait States: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug { +/// +/// The [`Default`] trait defines the starting state. +pub trait States: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug + Default { type Iter: Iterator; /// Returns an iterator over all the state variants. - fn states() -> Self::Iter; + fn variants() -> Self::Iter; } /// The label of a [`Schedule`](super::Schedule) that runs whenever [`State`] @@ -25,7 +27,7 @@ pub struct OnEnter(pub S); #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] pub struct OnExit(pub S); -/// A [`SystemSet`] that will run within \ when this state is active. +/// A [`SystemSet`] that will run within `CoreSet::StateTransitions` when this state is active. /// /// This is provided for convenience. A more general [`state_equals`](super::state_equals) /// [condition](super::Condition) also exists for systems that need to run elsewhere. @@ -38,13 +40,15 @@ pub struct OnUpdate(pub S); /// The current state value can be accessed through this resource. To *change* the state, /// queue a transition in the [`NextState`] resource, and it will be applied by the next /// [`apply_state_transition::`] system. -#[derive(Resource)] +/// +/// The starting state is defined via the [`Default`] implementation for `S`. +#[derive(Resource, Default)] pub struct State(pub S); /// The next state of [`State`]. /// /// To queue a transition, just set the contained value to `Some(next_state)`. -#[derive(Resource)] +#[derive(Resource, Default)] pub struct NextState(pub Option); /// If a new state is queued in [`NextState`], this system: From 7bea2e747ff7499f17e26ed9a444299cf89f2850 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 12:04:10 -0500 Subject: [PATCH 048/247] Remove add_set method --- crates/bevy_app/src/app.rs | 13 +------------ crates/bevy_ecs/src/schedule/schedule.rs | 10 ++-------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 63fddf918298a..ce2bbd945c5e1 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -445,12 +445,8 @@ impl App { pub fn add_default_sets(&mut self) -> &mut Self { // Adding sets let startup_schedule = self.schedule_mut(CoreSchedule::Startup).unwrap(); - startup_schedule.add_set(StartupSet::PreStartup); - startup_schedule.add_set(StartupSet::Startup); - startup_schedule.add_set(StartupSet::PostStartup); - - // Ordering startup_schedule.configure_set(StartupSet::PreStartup.before(StartupSet::Startup)); + startup_schedule.configure_set(StartupSet::Startup); startup_schedule.configure_set(StartupSet::PostStartup.after(StartupSet::Startup)); // Flush points @@ -472,13 +468,6 @@ impl App { // Adding sets let main_schedule = self.schedule_mut(CoreSchedule::Main).unwrap(); - main_schedule.add_set(CoreSet::First); - main_schedule.add_set(CoreSet::PreUpdate); - main_schedule.add_set(CoreSet::Update); - main_schedule.add_set(CoreSet::PostUpdate); - main_schedule.add_set(CoreSet::Last); - - // Ordering main_schedule.configure_set(CoreSet::First.before(CoreSet::PreUpdate)); main_schedule.configure_set(CoreSet::PreUpdate.before(CoreSet::Update)); main_schedule.configure_set(CoreSet::StateTransitions.before(CoreSet::Update)); diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 79bc5e090bb3a..cbf63c48349db 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -129,19 +129,13 @@ impl Schedule { self } - /// Adds an empty system set to the schedule. - pub fn add_set(&mut self, set: impl SystemSet) { - let boxed_set = Box::new(set); - self.graph.add_set(boxed_set); - } - - /// Configure a system set in this schedule. + /// Configures a system set in this schedule, adding it if it does not exist. pub fn configure_set(&mut self, set: impl IntoSystemSetConfig) -> &mut Self { self.graph.configure_set(set); self } - /// Configure a collection of system sets in this schedule. + /// Configures a collection of system sets in this schedule, adding them if they does not exist. pub fn configure_sets(&mut self, sets: impl IntoSystemSetConfigs) -> &mut Self { self.graph.configure_sets(sets); self From 3ef0b50f72e9ddb5f9d5af7b29ff7b1560a689c3 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 12:24:17 -0500 Subject: [PATCH 049/247] Use outer schedule, rather than active schedule --- crates/bevy_app/src/app.rs | 67 ++++++------------------ crates/bevy_app/src/lib.rs | 31 ++++++++++- crates/bevy_ecs/src/schedule/schedule.rs | 6 --- 3 files changed, 47 insertions(+), 57 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index ce2bbd945c5e1..2e19e6a601bdb 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -69,6 +69,14 @@ pub struct App { /// Typically, it is not configured manually, but set by one of Bevy's built-in plugins. /// See `bevy::winit::WinitPlugin` and [`ScheduleRunnerPlugin`](crate::schedule_runner::ScheduleRunnerPlugin). pub runner: Box, + /// The schedule that systems are added to by default. + /// + /// This is initially set to [`CoreSchedule::Main`]. + pub default_schedule_label: BoxedScheduleLabel, + /// The schedule that controls the outer loop of schedule execution. + /// + /// This is initially set to [`CoreSchedule::Outer`]. + pub outer_schedule_label: BoxedScheduleLabel, sub_apps: HashMap, plugin_registry: Vec>, plugin_name_added: HashSet, @@ -93,10 +101,9 @@ struct SubApp { } impl SubApp { - /// Runs the `SubApp`'s schedule. + /// Runs the `SubApp`'s default schedule. pub fn run(&mut self) { - let default_schedule = self.app.default_schedule().unwrap(); - self.app.world.run_schedule(default_schedule); + self.app.world.run_schedule(self.app.default_schedule_label); self.app.world.clear_trackers(); } @@ -156,6 +163,8 @@ impl App { sub_apps: HashMap::default(), plugin_registry: Vec::default(), plugin_name_added: Default::default(), + default_schedule_label: Box::new(CoreSchedule::Main), + outer_schedule_label: Box::new(CoreSchedule::Outer), is_building_plugin: false, } } @@ -163,9 +172,12 @@ impl App { /// Advances the execution of the [`Schedule`] by one cycle. /// /// This method also updates sub apps. - /// /// See [`add_sub_app`](Self::add_sub_app) and [`run_once`](Schedule::run_once) for more details. /// + /// The schedule run by this method is determined by the [`outer_schedule_label`](App) field. + /// In normal usage, this is [`CoreSchedule::Outer`], which will run [`CoreSchedule::Startup`] + /// the first time the app is run, and [`CoreSchedule::Main`] afterwards. + /// /// # Panics /// /// The active schedule of the app must be set before this method is called. @@ -173,14 +185,7 @@ impl App { #[cfg(feature = "trace")] let _bevy_frame_update_span = info_span!("frame").entered(); - let active_schedule = self.active_schedule().unwrap(); - self.run_schedule(active_schedule); - - // After the startup schedule has been called once, transition to the main schedule - let boxed_startup_label: BoxedScheduleLabel = Box::new(CoreSchedule::Startup); - if self.active_schedule().unwrap() == boxed_startup_label { - self.set_active_schedule(CoreSchedule::Main); - } + self.run_schedule(self.outer_schedule_label); for sub_app in self.sub_apps.values_mut() { sub_app.extract(&mut self.world); @@ -938,44 +943,6 @@ impl App { schedules.get_mut(&label) } - /// Sets the [`Schedule`] that will be modified by default when you call `App::add_system` - /// and similar methods. - /// - /// **Note:** This will create the schedule if it does not already exist. - pub fn set_default_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self { - let mut schedules = self.world.resource_mut::(); - - schedules.default_schedule_label = Some(Box::new(label)); - if schedules.get(&label).is_none() { - schedules.insert(label, Schedule::new()); - } - - self - } - - /// Gets the label of the [`Schedule`] that will be modified by default when you call `App::add_system` - /// and similar methods. - pub fn default_schedule(&self) -> &Option { - let schedules = self.world.resource::(); - &schedules.default_schedule_label - } - - /// Sets the [`Schedule`] that will be run the next time that [`App::update`] or [`App::run`] is called. - pub fn set_active_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self { - let mut schedules = self.world.resource_mut::(); - - schedules.active_schedule_label = Some(Box::new(label)); - - self - } - - /// Gets the label of the [`Schedule`] that will be modified by default when you call `App::add_system` - /// and similar methods. - pub fn active_schedule(&self) -> &Option { - let schedules = self.world.resource::(); - &schedules.active_schedule_label - } - /// Applies the function to the [`Schedule`] associated with `label`. /// /// **Note:** This will create the schedule if it does not already exist. diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index b84fdea1d5973..64df6f5e4a357 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -25,7 +25,11 @@ pub mod prelude { pub use crate::{app::App, CoreSet, DynamicPlugin, Plugin, PluginGroup, StartupSet}; } -use bevy_ecs::schedule::{ScheduleLabel, SystemSet}; +use bevy_ecs::{ + schedule::{Schedule, ScheduleLabel, SystemSet}, + system::Local, + world::World, +}; /// The names of the default [`App`] schedules. /// @@ -36,6 +40,31 @@ pub enum CoreSchedule { Startup, /// The schedule that contains the app logic that is evaluated each tick of [`App::update()`]. Main, + /// The schedule that controls which schedules run. + /// + /// This is typically created using the [`CoreSchedule::outer_schedule`] method, + /// and does not need to manipulated during ordinary use. + Outer, +} + +impl CoreSchedule { + /// An exclusive system that controls which schedule should be running. + /// + /// [`CoreSchedule::Startup`] will run a single time, and then [`CoreSchedule::Main`] will run on every later update. + pub fn outer_loop(world: &mut World, run_at_least_once: Local) { + if !*run_at_least_once { + world.run_schedule(CoreSchedule::Main); + } else { + world.run_schedule(CoreSchedule::Startup); + } + } + + /// Initializes a schedule for [`CoreSchedule::Outer`] that contains the [`outer_loop`] system. + pub fn outer_schedule() -> Schedule { + let schedule = Schedule::new(); + schedule.add_system(Self::outer_loop); + schedule + } } /// The names of the default [`App`] system sets. diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index cbf63c48349db..5b12079706366 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -27,10 +27,6 @@ use crate::{ #[derive(Default, Resource)] pub struct Schedules { inner: HashMap, - /// The schedule that systems are added to by default - pub default_schedule_label: Option, - /// The currently active `Schedule`, which should be run next. - pub active_schedule_label: Option, } impl Schedules { @@ -38,8 +34,6 @@ impl Schedules { pub fn new() -> Self { Self { inner: HashMap::new(), - default_schedule_label: None, - active_schedule_label: None, } } From 366e8670108744fcdb5dcb56cb9b15f13b4d2dd1 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 14:04:39 -0500 Subject: [PATCH 050/247] Hard code which schedule is run in App::update --- crates/bevy_app/src/app.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 650c65f83c07c..165b8f09df91b 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -73,10 +73,6 @@ pub struct App { /// /// This is initially set to [`CoreSchedule::Main`]. pub default_schedule_label: BoxedScheduleLabel, - /// The schedule that controls the outer loop of schedule execution. - /// - /// This is initially set to [`CoreSchedule::Outer`]. - pub outer_schedule_label: BoxedScheduleLabel, sub_apps: HashMap, plugin_registry: Vec>, plugin_name_added: HashSet, @@ -101,9 +97,9 @@ struct SubApp { } impl SubApp { - /// Runs the `SubApp`'s default schedule. + /// Runs the `SubApp`'s [`CoreSchedule::Main`] schedule. pub fn run(&mut self) { - self.app.world.run_schedule(self.app.default_schedule_label); + self.app.world.run_schedule(CoreSchedule::Main); self.app.world.clear_trackers(); } @@ -164,7 +160,6 @@ impl App { plugin_registry: Vec::default(), plugin_name_added: Default::default(), default_schedule_label: Box::new(CoreSchedule::Main), - outer_schedule_label: Box::new(CoreSchedule::Outer), is_building_plugin: false, } } @@ -174,8 +169,8 @@ impl App { /// This method also updates sub apps. /// See [`add_sub_app`](Self::add_sub_app) and [`run_once`](Schedule::run_once) for more details. /// - /// The schedule run by this method is determined by the [`outer_schedule_label`](App) field. - /// In normal usage, this is [`CoreSchedule::Outer`], which will run [`CoreSchedule::Startup`] + /// The [`CoreSchedule::Outer`] is run once. + /// With default configuration this will run [`CoreSchedule::Startup`] /// the first time the app is run, and [`CoreSchedule::Main`] afterwards. /// /// # Panics @@ -185,7 +180,7 @@ impl App { #[cfg(feature = "trace")] let _bevy_frame_update_span = info_span!("frame").entered(); - self.run_schedule(self.outer_schedule_label); + self.run_schedule(CoreSchedule::Outer); for sub_app in self.sub_apps.values_mut() { sub_app.extract(&mut self.world); From 777f38142b42d08949836420d0a1b1a5642b0499 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 14:04:39 -0500 Subject: [PATCH 051/247] Revert "Hard code which schedule is run in App::update" This reverts commit 366e8670108744fcdb5dcb56cb9b15f13b4d2dd1. --- crates/bevy_app/src/app.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 165b8f09df91b..650c65f83c07c 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -73,6 +73,10 @@ pub struct App { /// /// This is initially set to [`CoreSchedule::Main`]. pub default_schedule_label: BoxedScheduleLabel, + /// The schedule that controls the outer loop of schedule execution. + /// + /// This is initially set to [`CoreSchedule::Outer`]. + pub outer_schedule_label: BoxedScheduleLabel, sub_apps: HashMap, plugin_registry: Vec>, plugin_name_added: HashSet, @@ -97,9 +101,9 @@ struct SubApp { } impl SubApp { - /// Runs the `SubApp`'s [`CoreSchedule::Main`] schedule. + /// Runs the `SubApp`'s default schedule. pub fn run(&mut self) { - self.app.world.run_schedule(CoreSchedule::Main); + self.app.world.run_schedule(self.app.default_schedule_label); self.app.world.clear_trackers(); } @@ -160,6 +164,7 @@ impl App { plugin_registry: Vec::default(), plugin_name_added: Default::default(), default_schedule_label: Box::new(CoreSchedule::Main), + outer_schedule_label: Box::new(CoreSchedule::Outer), is_building_plugin: false, } } @@ -169,8 +174,8 @@ impl App { /// This method also updates sub apps. /// See [`add_sub_app`](Self::add_sub_app) and [`run_once`](Schedule::run_once) for more details. /// - /// The [`CoreSchedule::Outer`] is run once. - /// With default configuration this will run [`CoreSchedule::Startup`] + /// The schedule run by this method is determined by the [`outer_schedule_label`](App) field. + /// In normal usage, this is [`CoreSchedule::Outer`], which will run [`CoreSchedule::Startup`] /// the first time the app is run, and [`CoreSchedule::Main`] afterwards. /// /// # Panics @@ -180,7 +185,7 @@ impl App { #[cfg(feature = "trace")] let _bevy_frame_update_span = info_span!("frame").entered(); - self.run_schedule(CoreSchedule::Outer); + self.run_schedule(self.outer_schedule_label); for sub_app in self.sub_apps.values_mut() { sub_app.extract(&mut self.world); From a1da867c83e78dc50879dd16b5778f8d195e6eb8 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 14:30:20 -0500 Subject: [PATCH 052/247] Active and default schedules have been removed --- crates/bevy_app/src/app.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 650c65f83c07c..9a28365eca090 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -423,9 +423,6 @@ impl App { /// /// The schedules are defined in the [`CoreSchedule`] enum. /// - /// The [default schedule](App::set_default_schedule) is set to [`CoreSchedule::Main`]. - /// The [active schedule] is set to [`CoreSchedule::Startup`]. - /// /// You can also add standardized system sets to these schedules using [`App::add_default_sets`], /// which must be called *after* this method as it relies on these schedules existing. /// @@ -440,9 +437,6 @@ impl App { self.add_schedule(CoreSchedule::Startup, Schedule::new()); self.add_schedule(CoreSchedule::Main, Schedule::new()); - self.set_default_schedule(CoreSchedule::Main); - self.set_active_schedule(CoreSchedule::Startup); - self } From b221923780bc9b343c7702f5e5938dc975dc73bd Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 14:40:08 -0500 Subject: [PATCH 053/247] Implement *Label for Box --- crates/bevy_utils/src/label.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/bevy_utils/src/label.rs b/crates/bevy_utils/src/label.rs index d3e816552bb1e..e04dfa1ee5c81 100644 --- a/crates/bevy_utils/src/label.rs +++ b/crates/bevy_utils/src/label.rs @@ -98,6 +98,14 @@ macro_rules! define_boxed_label { self.dyn_clone() } } + + impl ScheduleLabel for Box { + fn dyn_clone(&self) -> Box { + // Be explicit that we want to use the inner value + // to avoid infinite recursion. + (**self).dyn_clone() + } + } }; } From c981a734349c6a0745afa864cd64b32acb39504c Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 14:56:24 -0500 Subject: [PATCH 054/247] Improve bevy_ecs prelude --- crates/bevy_app/src/app.rs | 11 ++--------- crates/bevy_ecs/src/lib.rs | 6 ++++-- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 9a28365eca090..4cd5401fafd86 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1,15 +1,8 @@ use crate::{CoreSchedule, CoreSet, Plugin, PluginGroup, StartupSet}; pub use bevy_derive::AppLabel; use bevy_ecs::{ - event::{Event, Events}, - prelude::FromWorld, - schedule::{ - apply_state_transition, apply_system_buffers, BoxedScheduleLabel, IntoSystemConfig, - IntoSystemConfigs, NextState, OnEnter, OnExit, OnUpdate, Schedule, ScheduleLabel, - Schedules, State, States, - }, - system::Resource, - world::World, + prelude::*, + schedule::{apply_state_transition, BoxedScheduleLabel, ScheduleLabel}, }; use bevy_utils::{tracing::debug, HashMap, HashSet}; use std::fmt::Debug; diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 80d03a8acbc36..1280adcbde41b 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -31,10 +31,12 @@ pub mod prelude { change_detection::{DetectChanges, DetectChangesMut}, component::Component, entity::Entity, - event::{EventReader, EventWriter, Events}, + event::{Event, EventReader, EventWriter, Events}, query::{Added, AnyOf, ChangeTrackers, Changed, Or, QueryState, With, Without}, schedule::{ - IntoSystemConfig, IntoSystemConfigs, Schedule, Schedules, State, States, SystemSet, + apply_state_transition, apply_system_buffers, IntoSystemConfig, IntoSystemConfigs, + IntoSystemSet, IntoSystemSetConfig, NextState, OnEnter, OnExit, OnUpdate, Schedule, + Schedules, State, States, SystemSet, }, system::{ adapter as system_adapter, From 2552d9702d0a93fa24b02260a7d0dcf9933b6f1f Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 14:58:37 -0500 Subject: [PATCH 055/247] Fix incorrect usage of default_schedule_label --- crates/bevy_app/src/app.rs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 4cd5401fafd86..17c8867302f34 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -283,14 +283,11 @@ impl App { pub fn add_system

(&mut self, system: impl IntoSystemConfig

) -> &mut Self { let schedules = self.world.resource_mut::(); - if let Some(default_schedule_label) = schedules.default_schedule_label { - if let Some(default_schedule) = schedules.get_mut(&default_schedule_label) { - default_schedule.add_system(system); - } else { - panic!("Default schedule does not exist.") - } + if let Some(default_schedule) = schedules.get_mut(&self.default_schedule_label) { + default_schedule.add_system(system); } else { - panic!("No default schedule set for the `App`.") + let schedule_label = self.default_schedule_label; + panic!("Default schedule {schedule_label:?} does not exist.") } self @@ -314,14 +311,11 @@ impl App { pub fn add_systems

(&mut self, systems: impl IntoSystemConfigs

) -> &mut Self { let schedules = self.world.resource_mut::(); - if let Some(default_schedule_label) = schedules.default_schedule_label { - if let Some(default_schedule) = schedules.get_mut(&default_schedule_label) { - default_schedule.add_systems(systems); - } else { - panic!("Default schedule does not exist.") - } + if let Some(default_schedule) = schedules.get_mut(&self.default_schedule_label) { + default_schedule.add_systems(systems); } else { - panic!("No default schedule set for the `App`.") + let schedule_label = self.default_schedule_label; + panic!("Default schedule {schedule_label:?} does not exist.") } self From 92aff8a1ac9a0a1cf1027d3bfcf5e57cfe0b88aa Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 15:06:38 -0500 Subject: [PATCH 056/247] Export common_conditions in the prelude --- crates/bevy_app/src/app.rs | 5 ++++- crates/bevy_ecs/src/lib.rs | 6 +++--- crates/bevy_ecs/src/schedule/condition.rs | 4 +--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 17c8867302f34..ab0f6fcc270d6 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -242,6 +242,9 @@ impl App { /// which run during [`CoreSet::StateTransitions`] after the transitions are applied. /// These systems sets only run if the [`State`] resource matches their label. /// + /// If you would like to control how other systems run based on the current state, + /// you can emulate this behavior using the [`state_equals`] [`Condition`](bevy_ecs::schedule::Condition). + /// /// Note that you can also apply state tranitions at other points in the schedule /// by adding the [`apply_state_transition`] system manually. pub fn add_state(&mut self) -> &mut Self { @@ -256,7 +259,7 @@ impl App { main_schedule.configure_set( OnUpdate(variant) .in_set(CoreSet::StateTransitions) - .run_if_state_equals(variant) + .run_if(state_equals(variant)) .after(apply_state_transition::), ); } diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 1280adcbde41b..687fb0b1e9b9e 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -34,9 +34,9 @@ pub mod prelude { event::{Event, EventReader, EventWriter, Events}, query::{Added, AnyOf, ChangeTrackers, Changed, Or, QueryState, With, Without}, schedule::{ - apply_state_transition, apply_system_buffers, IntoSystemConfig, IntoSystemConfigs, - IntoSystemSet, IntoSystemSetConfig, NextState, OnEnter, OnExit, OnUpdate, Schedule, - Schedules, State, States, SystemSet, + apply_state_transition, apply_system_buffers, common_conditions::*, IntoSystemConfig, + IntoSystemConfigs, IntoSystemSet, IntoSystemSetConfig, NextState, OnEnter, OnExit, + OnUpdate, Schedule, Schedules, State, States, SystemSet, }, system::{ adapter as system_adapter, diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index edd0456a004c7..79b88f517c066 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -1,5 +1,3 @@ -pub use common_conditions::*; - use crate::system::BoxedSystem; pub type BoxedCondition = BoxedSystem<(), bool>; @@ -26,7 +24,7 @@ mod sealed { } } -mod common_conditions { +pub mod common_conditions { use crate::schedule::{State, States}; use crate::system::{Res, Resource}; From 780a5742cc773f42ee9979db8740f6127fbf7e02 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 15:16:42 -0500 Subject: [PATCH 057/247] Make run_schedule take a &Label --- crates/bevy_app/src/app.rs | 8 +++++--- crates/bevy_app/src/lib.rs | 4 ++-- crates/bevy_ecs/src/schedule/schedule.rs | 11 +++++++++++ crates/bevy_ecs/src/schedule/state.rs | 4 ++-- crates/bevy_ecs/src/world/mod.rs | 9 ++++++--- 5 files changed, 26 insertions(+), 10 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index ab0f6fcc270d6..7b43dee3e0ee9 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -96,7 +96,9 @@ struct SubApp { impl SubApp { /// Runs the `SubApp`'s default schedule. pub fn run(&mut self) { - self.app.world.run_schedule(self.app.default_schedule_label); + self.app + .world + .run_schedule(&self.app.default_schedule_label); self.app.world.clear_trackers(); } @@ -178,7 +180,7 @@ impl App { #[cfg(feature = "trace")] let _bevy_frame_update_span = info_span!("frame").entered(); - self.run_schedule(self.outer_schedule_label); + self.run_schedule(&self.outer_schedule_label); for sub_app in self.sub_apps.values_mut() { sub_app.extract(&mut self.world); @@ -927,7 +929,7 @@ impl App { } /// Runs the [`Schedule`] with the provided `label` on the app's [`World`] a single time. - pub fn run_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self { + pub fn run_schedule(&mut self, label: &impl ScheduleLabel) -> &mut Self { self.world.run_schedule(label); self diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index 64df6f5e4a357..b581eb8c020b8 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -53,9 +53,9 @@ impl CoreSchedule { /// [`CoreSchedule::Startup`] will run a single time, and then [`CoreSchedule::Main`] will run on every later update. pub fn outer_loop(world: &mut World, run_at_least_once: Local) { if !*run_at_least_once { - world.run_schedule(CoreSchedule::Main); + world.run_schedule(&CoreSchedule::Main); } else { - world.run_schedule(CoreSchedule::Startup); + world.run_schedule(&CoreSchedule::Startup); } } diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 209e09a1e65f5..f6ae5534acc17 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -57,6 +57,17 @@ impl Schedules { self.inner.remove(label) } + /// Removes the (schedule, label) pair corresponding to the `label` from the map, returning it if it existed. + pub fn remove_entry( + &mut self, + label: &dyn ScheduleLabel, + ) -> Option<(Box, Schedule)> { + if !self.inner.contains_key(label) { + warn!("schedule with label {:?} not found", label); + } + self.inner.remove_entry(label) + } + /// Returns a reference to the schedule associated with `label`, if it exists. pub fn get(&self, label: &dyn ScheduleLabel) -> Option<&Schedule> { self.inner.get(label) diff --git a/crates/bevy_ecs/src/schedule/state.rs b/crates/bevy_ecs/src/schedule/state.rs index 11f0e25db02e8..4f13c11732337 100644 --- a/crates/bevy_ecs/src/schedule/state.rs +++ b/crates/bevy_ecs/src/schedule/state.rs @@ -62,7 +62,7 @@ pub fn apply_state_transition(world: &mut World) { &mut world.resource_mut::>().0, entered_state.clone(), ); - world.run_schedule(OnExit(exited_state)); - world.run_schedule(OnEnter(entered_state)); + world.run_schedule(&OnExit(exited_state)); + world.run_schedule(&OnEnter(entered_state)); } } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index f3f365179a2f6..e0cb196ce3bee 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1989,10 +1989,13 @@ impl World { /// and system state is cached. /// /// For simple testing use cases, call [`Schedule::run(world)`](Schedule::run) instead. - pub fn run_schedule(&mut self, label: impl ScheduleLabel) { - if let Some(mut schedule) = self.resource_mut::().remove(&label) { + pub fn run_schedule(&mut self, label: &impl ScheduleLabel) { + if let Some((extracted_label, mut schedule)) = + self.resource_mut::().remove_entry(label) + { schedule.run(self); - self.resource_mut::().insert(label, schedule); + self.resource_mut::() + .insert(extracted_label, schedule); } } } From 1c8e0755f50beab610e0120a3b679f233d38ef8d Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 15:38:06 -0500 Subject: [PATCH 058/247] Borrow checker fixes --- crates/bevy_app/src/app.rs | 52 +++++++++++++++------------ crates/bevy_app/src/lib.rs | 2 +- crates/bevy_ecs/src/schedule/state.rs | 4 ++- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 7b43dee3e0ee9..a8815d464c939 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -180,7 +180,7 @@ impl App { #[cfg(feature = "trace")] let _bevy_frame_update_span = info_span!("frame").entered(); - self.run_schedule(&self.outer_schedule_label); + self.run_schedule(&self.outer_schedule_label.dyn_clone()); for sub_app in self.sub_apps.values_mut() { sub_app.extract(&mut self.world); @@ -253,11 +253,9 @@ impl App { self.init_resource::>(); self.init_resource::>(); self.add_system(apply_state_transition::.in_set(CoreSet::StateTransitions)); - let main_schedule = self.schedule_mut(CoreSchedule::Main).unwrap(); + let main_schedule = self.schedule_mut(&CoreSchedule::Main).unwrap(); for variant in S::variants() { - self.add_schedule(OnEnter(variant), Schedule::new()); - self.add_schedule(OnExit(variant), Schedule::new()); main_schedule.configure_set( OnUpdate(variant) .in_set(CoreSet::StateTransitions) @@ -266,6 +264,12 @@ impl App { ); } + // These are two different for loops to avoid conflicting access to self + for variant in S::variants() { + self.add_schedule(OnEnter(variant), Schedule::new()); + self.add_schedule(OnExit(variant), Schedule::new()); + } + self } @@ -286,12 +290,12 @@ impl App { /// app.add_system(my_system); /// ``` pub fn add_system

(&mut self, system: impl IntoSystemConfig

) -> &mut Self { - let schedules = self.world.resource_mut::(); + let mut schedules = self.world.resource_mut::(); if let Some(default_schedule) = schedules.get_mut(&self.default_schedule_label) { default_schedule.add_system(system); } else { - let schedule_label = self.default_schedule_label; + let schedule_label = &self.default_schedule_label; panic!("Default schedule {schedule_label:?} does not exist.") } @@ -314,12 +318,12 @@ impl App { /// app.add_systems((system_a, system_b, system_c)); /// ``` pub fn add_systems

(&mut self, systems: impl IntoSystemConfigs

) -> &mut Self { - let schedules = self.world.resource_mut::(); + let mut schedules = self.world.resource_mut::(); if let Some(default_schedule) = schedules.get_mut(&self.default_schedule_label) { default_schedule.add_systems(systems); } else { - let schedule_label = self.default_schedule_label; + let schedule_label = &self.default_schedule_label; panic!("Default schedule {schedule_label:?} does not exist.") } @@ -332,7 +336,7 @@ impl App { system: impl IntoSystemConfig

, schedule_label: impl ScheduleLabel, ) -> &mut Self { - let schedules = self.world.resource_mut::(); + let mut schedules = self.world.resource_mut::(); if let Some(schedule) = schedules.get_mut(&schedule_label) { schedule.add_system(system); @@ -349,7 +353,7 @@ impl App { systems: impl IntoSystemConfigs

, schedule_label: impl ScheduleLabel, ) -> &mut Self { - let schedules = self.world.resource_mut::(); + let mut schedules = self.world.resource_mut::(); if let Some(schedule) = schedules.get_mut(&schedule_label) { schedule.add_systems(systems); @@ -453,7 +457,7 @@ impl App { /// ``` pub fn add_default_sets(&mut self) -> &mut Self { // Adding sets - let startup_schedule = self.schedule_mut(CoreSchedule::Startup).unwrap(); + let startup_schedule = self.schedule_mut(&CoreSchedule::Startup).unwrap(); startup_schedule.configure_set(StartupSet::PreStartup.before(StartupSet::Startup)); startup_schedule.configure_set(StartupSet::Startup); startup_schedule.configure_set(StartupSet::PostStartup.after(StartupSet::Startup)); @@ -476,7 +480,7 @@ impl App { startup_schedule.set_default_set(StartupSet::Startup); // Adding sets - let main_schedule = self.schedule_mut(CoreSchedule::Main).unwrap(); + let main_schedule = self.schedule_mut(&CoreSchedule::Main).unwrap(); main_schedule.configure_set(CoreSet::First.before(CoreSet::PreUpdate)); main_schedule.configure_set(CoreSet::PreUpdate.before(CoreSet::Update)); main_schedule.configure_set(CoreSet::StateTransitions.before(CoreSet::Update)); @@ -936,15 +940,17 @@ impl App { } /// Gets read-only access to the [`Schedule`] with the provided `label` if it exists. - pub fn schedule(&self, label: impl ScheduleLabel) -> Option<&Schedule> { + pub fn schedule(&self, label: &impl ScheduleLabel) -> Option<&Schedule> { let schedules = self.world.get_resource::()?; - schedules.get(&label) + schedules.get(label) } /// Gets read-write access to a [`Schedule`] with the provided `label` if it exists. - pub fn schedule_mut(&self, label: impl ScheduleLabel) -> Option<&mut Schedule> { - let schedules = self.world.get_resource::()?; - schedules.get_mut(&label) + pub fn schedule_mut(&mut self, label: &impl ScheduleLabel) -> Option<&mut Schedule> { + let schedules = self.world.get_resource_mut::()?; + // We need to call .into_inner here to satisfy the borrow checker: + // it can reason about reborrows using ordinary references but not the `Mut` smart pointer. + schedules.into_inner().get_mut(label) } /// Applies the function to the [`Schedule`] associated with `label`. @@ -952,16 +958,16 @@ impl App { /// **Note:** This will create the schedule if it does not already exist. pub fn edit_schedule( &mut self, - label: impl ScheduleLabel, - f: impl FnMut(&mut Schedule), + label: &impl ScheduleLabel, + mut f: impl FnMut(&mut Schedule), ) -> &mut Self { - let mut schedules = self.world.resource::(); + let mut schedules = self.world.resource_mut::(); - if schedules.get(&label).is_none() { - schedules.insert(label, Schedule::new()); + if schedules.get(label).is_none() { + schedules.insert(label.dyn_clone(), Schedule::new()); } - let schedule = schedules.get_mut(&label).unwrap(); + let schedule = schedules.get_mut(label).unwrap(); // Call the function f, passing in the schedule retrieved f(schedule); diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index b581eb8c020b8..2c338ff21bf6a 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -61,7 +61,7 @@ impl CoreSchedule { /// Initializes a schedule for [`CoreSchedule::Outer`] that contains the [`outer_loop`] system. pub fn outer_schedule() -> Schedule { - let schedule = Schedule::new(); + let mut schedule = Schedule::new(); schedule.add_system(Self::outer_loop); schedule } diff --git a/crates/bevy_ecs/src/schedule/state.rs b/crates/bevy_ecs/src/schedule/state.rs index 4f13c11732337..a75221dde0ed1 100644 --- a/crates/bevy_ecs/src/schedule/state.rs +++ b/crates/bevy_ecs/src/schedule/state.rs @@ -10,7 +10,9 @@ use crate::world::World; /// Types that can define states in a finite-state machine. /// /// The [`Default`] trait defines the starting state. -pub trait States: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug + Default { +pub trait States: + 'static + Send + Sync + Clone + Copy + PartialEq + Eq + Hash + Debug + Default +{ type Iter: Iterator; /// Returns an iterator over all the state variants. From f278e2ed4c8671d2ccd299b55e7e17bd931b1640 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 15:43:56 -0500 Subject: [PATCH 059/247] Clean up imports --- crates/bevy_core/src/lib.rs | 1 - crates/bevy_input/src/lib.rs | 2 +- crates/bevy_time/src/fixed_timestep.rs | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/bevy_core/src/lib.rs b/crates/bevy_core/src/lib.rs index cbbc4c0b76198..6092df00fc751 100644 --- a/crates/bevy_core/src/lib.rs +++ b/crates/bevy_core/src/lib.rs @@ -29,7 +29,6 @@ use std::ops::Range; use std::path::PathBuf; #[cfg(not(target_arch = "wasm32"))] -use bevy_ecs::schedule::IntoSystemDescriptor; #[cfg(not(target_arch = "wasm32"))] use bevy_tasks::tick_global_task_pools_on_main_thread; diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index a7e6383406479..374dc494758fa 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -22,7 +22,7 @@ pub mod prelude { } use bevy_app::prelude::*; -use bevy_ecs::schedule::{IntoSystemDescriptor, SystemSet, SystemSet}; +use bevy_ecs::prelude::*; use bevy_reflect::{FromReflect, Reflect}; use keyboard::{keyboard_input_system, KeyCode, KeyboardInput, ScanCode}; use mouse::{ diff --git a/crates/bevy_time/src/fixed_timestep.rs b/crates/bevy_time/src/fixed_timestep.rs index fa355706581bb..ae71576a0abe8 100644 --- a/crates/bevy_time/src/fixed_timestep.rs +++ b/crates/bevy_time/src/fixed_timestep.rs @@ -3,7 +3,6 @@ use bevy_ecs::{ archetype::ArchetypeComponentId, component::ComponentId, query::Access, - schedule::ShouldRun, system::{IntoSystem, Res, ResMut, Resource, System}, world::World, }; From a67a5a1a559ec4e73311597ba78b3d26e19b572b Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 15:46:03 -0500 Subject: [PATCH 060/247] Remove .at_end from tick_global_task_pools_on_main_thread --- crates/bevy_core/src/lib.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/bevy_core/src/lib.rs b/crates/bevy_core/src/lib.rs index 6092df00fc751..88bc56f8fd607 100644 --- a/crates/bevy_core/src/lib.rs +++ b/crates/bevy_core/src/lib.rs @@ -20,7 +20,7 @@ pub mod prelude { } use bevy_app::prelude::*; -use bevy_ecs::entity::Entity; +use bevy_ecs::prelude::*; use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; use bevy_utils::{Duration, HashSet, Instant}; use std::borrow::Cow; @@ -106,11 +106,7 @@ impl Plugin for TaskPoolPlugin { self.task_pool_options.create_default_pools(); #[cfg(not(target_arch = "wasm32"))] - app.add_system( - tick_global_task_pools_on_main_thread - .at_end() - .in_set(bevy_app::CoreSet::Last), - ); + app.add_system(tick_global_task_pools_on_main_thread.in_set(bevy_app::CoreSet::Last)); } } From a1ec0d7639228d50921225e2694b0fe76aa7d7b3 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 15:47:31 -0500 Subject: [PATCH 061/247] Fix more imports --- crates/bevy_gilrs/src/lib.rs | 2 +- crates/bevy_hierarchy/src/valid_parent_check_plugin.rs | 2 +- crates/bevy_window/src/lib.rs | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index 6abb3983a3f75..958dec3809fd4 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -2,7 +2,7 @@ mod converter; mod gilrs_system; use bevy_app::{App, CoreSet, Plugin, StartupSet}; -use bevy_ecs::schedule::IntoSystemDescriptor; +use bevy_ecs::prelude::*; use bevy_input::InputSystem; use bevy_utils::tracing::error; use gilrs::GilrsBuilder; diff --git a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs index 50f93337bdc78..e8382f7b600ea 100644 --- a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs +++ b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use bevy_app::{App, CoreSet, Plugin}; use bevy_core::Name; -use bevy_ecs::{prelude::*, schedule::ShouldRun}; +use bevy_ecs::prelude::*; use bevy_log::warn; use bevy_utils::{get_short_name, HashSet}; diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index bc8ef8326f7a3..8d1505365f030 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -22,12 +22,9 @@ pub mod prelude { } use bevy_app::prelude::*; -use bevy_ecs::schedule::{IntoSystemDescriptor, SystemSet}; +use bevy_ecs::prelude::*; use std::path::PathBuf; -use bevy_app::prelude::*; -use bevy_ecs::schedule::SystemLabel; - impl Default for WindowPlugin { fn default() -> Self { WindowPlugin { From 77f4314ff742b70ce70b3e630d584a3b92966ed5 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 15:54:16 -0500 Subject: [PATCH 062/247] Replace run criteria for ReportHierarchyIssue --- .../src/valid_parent_check_plugin.rs | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs index e8382f7b600ea..ddd07f19fbec1 100644 --- a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs +++ b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs @@ -19,6 +19,23 @@ pub struct ReportHierarchyIssue { pub enabled: bool, _comp: PhantomData, } + +impl ReportHierarchyIssue { + /// Constructs a new object + pub fn new(enabled: bool) -> Self { + ReportHierarchyIssue { + enabled, + _comp: Default::default(), + } + } +} + +impl PartialEq for ReportHierarchyIssue { + fn eq(&self, other: &Self) -> bool { + self.enabled == other.enabled + } +} + impl Default for ReportHierarchyIssue { fn default() -> Self { Self { @@ -59,11 +76,11 @@ pub fn check_hierarchy_component_has_valid_parent( } /// Run criteria that only allows running when [`ReportHierarchyIssue`] is enabled. -pub fn on_hierarchy_reports_enabled(report: Res>) -> ShouldRun +pub fn on_hierarchy_reports_enabled(report: Res>) -> bool where T: Component, { - report.enabled.into() + report.enabled } /// Print a warning for each `Entity` with a `T` component @@ -81,7 +98,7 @@ impl Plugin for ValidParentCheckPlugin { fn build(&self, app: &mut App) { app.init_resource::>().add_system( check_hierarchy_component_has_valid_parent:: - .with_run_criteria(on_hierarchy_reports_enabled::) + .run_if(resource_equals(ReportHierarchyIssue::::new(true))) .in_set(CoreSet::Last), ); } From 95490e61557f2d231130264db25e22a86c44f499 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Thu, 19 Jan 2023 15:55:02 -0500 Subject: [PATCH 063/247] TimeSystem is no longer an exclusive system --- crates/bevy_time/src/lib.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/crates/bevy_time/src/lib.rs b/crates/bevy_time/src/lib.rs index 68324ada2ed4c..fef7bf4f28f3c 100644 --- a/crates/bevy_time/src/lib.rs +++ b/crates/bevy_time/src/lib.rs @@ -39,14 +39,7 @@ impl Plugin for TimePlugin { .register_type::() .register_type::

{ - ( - systems::sync_simple_transforms, - systems::propagate_transforms, - ) +/// A [`Schedule`] that contains correctly ordered transform propagation systems for third party plugin use +pub fn transform_propagate_schedule() -> Schedule { + let mut schedule = Schedule::new(); + schedule + .add_system(systems::sync_simple_transforms.before(systems::propagate_transforms)) + .add_system(systems::propagate_transforms); + schedule } /// The base plugin for handling [`Transform`] components From bdaada499e15e3ba0b972b7a52cab6d021233f54 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 20 Jan 2023 12:09:35 -0500 Subject: [PATCH 075/247] Clean up and fix imports --- crates/bevy_animation/src/lib.rs | 10 +--------- crates/bevy_audio/src/lib.rs | 1 + crates/bevy_render/src/camera/mod.rs | 1 + crates/bevy_render/src/extract_resource.rs | 4 +--- crates/bevy_render/src/texture/mod.rs | 1 + 5 files changed, 5 insertions(+), 12 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index d4adec50b8ea6..d3cf85293975b 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -8,15 +8,7 @@ use std::time::Duration; use bevy_app::{App, CoreSet, Plugin}; use bevy_asset::{AddAsset, Assets, Handle}; use bevy_core::Name; -use bevy_ecs::{ - change_detection::{DetectChanges, Mut}, - entity::Entity, - prelude::Component, - query::With, - reflect::ReflectComponent, - schedule::IntoSystemDescriptor, - system::{Query, Res}, -}; +use bevy_ecs::prelude::*; use bevy_hierarchy::{Children, Parent}; use bevy_math::{Quat, Vec3}; use bevy_reflect::{FromReflect, Reflect, TypeUuid}; diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index b6eef96113912..5f9657e28d4a9 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -42,6 +42,7 @@ pub use rodio::Sample; use bevy_app::prelude::*; use bevy_asset::{AddAsset, Asset}; +use bevy_ecs::prelude::*; /// Adds support for audio playback to a Bevy Application /// diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index feb036296c1da..26a3b2ae86995 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -9,6 +9,7 @@ pub use projection::*; use crate::{render_graph::RenderGraph, RenderApp, RenderSet}; use bevy_app::{App, Plugin}; +use bevy_ecs::prelude::*; #[derive(Default)] pub struct CameraPlugin; diff --git a/crates/bevy_render/src/extract_resource.rs b/crates/bevy_render/src/extract_resource.rs index 10f6a1d901e4e..c899b06d4d788 100644 --- a/crates/bevy_render/src/extract_resource.rs +++ b/crates/bevy_render/src/extract_resource.rs @@ -1,10 +1,8 @@ use std::marker::PhantomData; use bevy_app::{App, Plugin}; -use bevy_ecs::change_detection::DetectChanges; +use bevy_ecs::prelude::*; #[cfg(debug_assertions)] -use bevy_ecs::system::Local; -use bevy_ecs::system::{Commands, Res, ResMut, Resource}; pub use bevy_render_macros::ExtractResource; use crate::{Extract, RenderApp, RenderSet}; diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index 78c9839a0f9b4..30da2e5a0e3b8 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -32,6 +32,7 @@ use crate::{ RenderApp, RenderSet, }; use bevy_app::{App, Plugin}; +use bevy_ecs::prelude::*; use bevy_asset::{AddAsset, Assets}; // TODO: replace Texture names with Image names? From c035d5ca978c7eead7e1352eae9e26dd336e4785 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 20 Jan 2023 12:16:38 -0500 Subject: [PATCH 076/247] Remove fresh add_system_to_stage --- crates/bevy_window/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index 62b292450feac..4b1865db12476 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -92,10 +92,10 @@ impl Plugin for WindowPlugin { match self.exit_condition { ExitCondition::OnPrimaryClosed => { - app.add_system_to_stage(CoreStage::PostUpdate, exit_on_primary_closed); + app.add_system(exit_on_primary_closed.in_set(CoreSet::PostUpdate)); } ExitCondition::OnAllClosed => { - app.add_system_to_stage(CoreStage::PostUpdate, exit_on_all_closed); + app.add_system(exit_on_all_closed.in_set(CoreSet::PostUpdate)); } ExitCondition::DontExit => {} } From 4053484c0369d7400e632032de329630caafa3ec Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 20 Jan 2023 12:19:17 -0500 Subject: [PATCH 077/247] Migrate bevy_transform system tests --- crates/bevy_transform/src/systems.rs | 30 +++++++++------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index 4de47a3656dfc..84eedc080498e 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -167,20 +167,14 @@ mod test { use crate::TransformBundle; use bevy_hierarchy::{BuildChildren, BuildWorldChildren, Children, Parent}; - #[derive(StageLabel)] - struct Update; - #[test] fn did_propagate() { ComputeTaskPool::init(TaskPool::default); let mut world = World::default(); - let mut update_stage = SystemStage::parallel(); - update_stage.add_system(sync_simple_transforms); - update_stage.add_system(propagate_transforms); - - let mut schedule = Schedule::default(); - schedule.add_stage(Update, update_stage); + let mut schedule = Schedule::new(); + schedule.add_system(sync_simple_transforms); + schedule.add_system(propagate_transforms); // Root entity world.spawn(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0))); @@ -217,12 +211,9 @@ mod test { fn did_propagate_command_buffer() { let mut world = World::default(); - let mut update_stage = SystemStage::parallel(); - update_stage.add_system(sync_simple_transforms); - update_stage.add_system(propagate_transforms); - - let mut schedule = Schedule::default(); - schedule.add_stage(Update, update_stage); + let mut schedule = Schedule::new(); + schedule.add_system(sync_simple_transforms); + schedule.add_system(propagate_transforms); // Root entity let mut queue = CommandQueue::default(); @@ -261,12 +252,9 @@ mod test { ComputeTaskPool::init(TaskPool::default); let mut world = World::default(); - let mut update_stage = SystemStage::parallel(); - update_stage.add_system(sync_simple_transforms); - update_stage.add_system(propagate_transforms); - - let mut schedule = Schedule::default(); - schedule.add_stage(Update, update_stage); + let mut schedule = Schedule::new(); + schedule.add_system(sync_simple_transforms); + schedule.add_system(propagate_transforms); // Add parent entities let mut children = Vec::new(); From 0f25c8f2c325838c946e98bd5d8ed2de2520cfec Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 20 Jan 2023 12:51:04 -0500 Subject: [PATCH 078/247] Migrate RenderStage -> RenderSet --- crates/bevy_render/src/lib.rs | 89 +++++++++++++++++++-------- crates/bevy_render/src/texture/mod.rs | 2 +- 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 99740cb8b502a..d8e501d4ba098 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -50,7 +50,7 @@ use crate::{ settings::WgpuSettings, view::{ViewPlugin, WindowRenderPlugin}, }; -use bevy_app::{App, AppLabel, Plugin}; +use bevy_app::{App, AppLabel, CoreSchedule, Plugin}; use bevy_asset::{AddAsset, AssetServer}; use bevy_ecs::{prelude::*, system::SystemState}; use bevy_utils::tracing::debug; @@ -65,7 +65,9 @@ pub struct RenderPlugin { pub wgpu_settings: WgpuSettings, } -/// The labels of the default App rendering stages. +/// The labels of the default App rendering sets. +/// +/// The sets run in the order listed, with [`apply_system_buffers`] inserted between each set. #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum RenderSet { /// Extract data from the "app world" and insert it into the "render world". @@ -96,6 +98,51 @@ pub enum RenderSet { Cleanup, } +impl RenderSet { + /// Sets up the base structure of the rendering [`Schedule`]. + /// + /// The sets in [`RenderSet`] are configured to run in order. + /// A copy of [`apply_system_buffers`] is inserted between each of the sets defined in [`RenderSet`]. + pub fn base_schedule() -> Schedule { + let mut schedule = Schedule::new(); + + // Buffer flushes + induced ordering + schedule.add_system( + apply_system_buffers + .after(RenderSet::Extract) + .before(RenderSet::ExtractCommands), + ); + schedule.add_system( + apply_system_buffers + .after(RenderSet::ExtractCommands) + .before(RenderSet::Prepare), + ); + schedule.add_system( + apply_system_buffers + .after(RenderSet::Prepare) + .before(RenderSet::Queue), + ); + schedule.add_system( + apply_system_buffers + .after(RenderSet::Queue) + .before(RenderSet::PhaseSort), + ); + schedule.add_system( + apply_system_buffers + .after(RenderSet::PhaseSort) + .before(RenderSet::Render), + ); + schedule.add_system( + apply_system_buffers + .after(RenderSet::Render) + .before(RenderSet::Cleanup), + ); + schedule.add_system(apply_system_buffers.after(RenderSet::Cleanup)); + + schedule + } +} + /// Resource for holding the extract stage of the rendering schedule. #[derive(Resource)] pub struct ExtractStage(pub SystemStage); @@ -178,8 +225,9 @@ impl Plugin for RenderPlugin { let asset_server = app.world.resource::().clone(); let mut render_app = App::empty(); - let mut extract_stage = - SystemStage::parallel().with_system(PipelineCache::extract_shaders); + let mut render_schedule = RenderSet::base_schedule(); + + render_schedule.add_system(PipelineCache::extract_shaders.in_set(RenderSet::Extract)); // Get the ComponentId for MainWorld. This does technically 'waste' a `WorldId`, but that's probably fine render_app.init_resource::(); render_app.world.remove_resource::(); @@ -196,28 +244,21 @@ impl Plugin for RenderPlugin { // See also https://github.com/bevyengine/bevy/issues/5082 extract_stage.set_apply_buffers(false); - // This stage applies the commands from the extract stage while the render schedule + // This set applies the commands from the extract stage while the render schedule // is running in parallel with the main app. - let mut extract_commands_stage = SystemStage::parallel(); - extract_commands_stage.add_system(apply_extract_commands.at_start()); + render_schedule.add_system(apply_extract_commands.in_set(RenderSet::ExtractCommands)); + + render_schedule.add_system( + PipelineCache::process_pipeline_queue_system + .before(render_system) + .in_set(RenderSet::Render), + ); + render_schedule.add_system(render_system.in_set(RenderSet::Render)); + + render_schedule.add_system(World::clear_entities.in_set(RenderSet::Cleanup)); + render_app - .add_stage(RenderStage::Extract, extract_stage) - .add_stage(RenderStage::ExtractCommands, extract_commands_stage) - .add_stage(RenderStage::Prepare, SystemStage::parallel()) - .add_stage(RenderStage::Queue, SystemStage::parallel()) - .add_stage(RenderStage::PhaseSort, SystemStage::parallel()) - .add_stage( - RenderSet::Render, - SystemStage::parallel() - // Note: Must run before `render_system` in order to - // processed newly queued pipelines. - .with_system(PipelineCache::process_pipeline_queue_system) - .with_system(render_system.at_end()), - ) - .add_stage( - RenderSet::Cleanup, - SystemStage::parallel().with_system(World::clear_entities.at_end()), - ) + .add_schedule(CoreSchedule::Main, render_schedule) .init_resource::() .insert_resource(RenderInstance(instance)) .insert_resource(device) diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index 01644c772d926..444bcfed6729b 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -106,7 +106,7 @@ impl Plugin for ImagePlugin { .add_system(update_texture_cache_system.in_set(RenderSet::Cleanup)) .init_resource::() .init_resource::() - .add_system_to_stage(RenderStage::Cleanup, update_texture_cache_system); + .add_system(update_texture_cache_system.in_set(RenderSet::Cleanup)); } } } From 43fdc3476e38783998278eb9db3359f1786b9d67 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 20 Jan 2023 12:53:35 -0500 Subject: [PATCH 079/247] Remove flush point after RenderSet::Extract --- crates/bevy_render/src/lib.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index d8e501d4ba098..ddf511cb28401 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -106,12 +106,14 @@ impl RenderSet { pub fn base_schedule() -> Schedule { let mut schedule = Schedule::new(); + // Don't apply buffers when the Extract stage finishes running + // This runs on the render world, but buffers are applied + // after access to the main world is removed + // See also https://github.com/bevyengine/bevy/issues/5082 + schedule.configure_set(RenderSet::Extract.before(RenderSet::ExtractCommands)); + // Buffer flushes + induced ordering - schedule.add_system( - apply_system_buffers - .after(RenderSet::Extract) - .before(RenderSet::ExtractCommands), - ); + schedule.add_system( apply_system_buffers .after(RenderSet::ExtractCommands) @@ -238,11 +240,6 @@ impl Plugin for RenderPlugin { // `Extract` systems must read from the main world. We want to emit an error when that doesn't occur // Safe to unwrap: Ensured it existed just above extract_stage.set_must_read_resource(main_world_in_render.unwrap()); - // don't apply buffers when the stage finishes running - // extract stage runs on the render world, but buffers are applied - // after access to the main world is removed - // See also https://github.com/bevyengine/bevy/issues/5082 - extract_stage.set_apply_buffers(false); // This set applies the commands from the extract stage while the render schedule // is running in parallel with the main app. From d2ddb20e0e953b339e6b9a3f1cf1b613eab83128 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 20 Jan 2023 12:55:05 -0500 Subject: [PATCH 080/247] Remove `set_must_read_resource` --- crates/bevy_render/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index ddf511cb28401..2385b530817e1 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -237,9 +237,6 @@ impl Plugin for RenderPlugin { .world .components() .get_resource_id(TypeId::of::()); - // `Extract` systems must read from the main world. We want to emit an error when that doesn't occur - // Safe to unwrap: Ensured it existed just above - extract_stage.set_must_read_resource(main_world_in_render.unwrap()); // This set applies the commands from the extract stage while the render schedule // is running in parallel with the main app. From 3929c854c5b20e2cf54d268d65717cbb711b0508 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 20 Jan 2023 17:33:49 -0500 Subject: [PATCH 081/247] Outline strategy of pipelined rendering migration --- crates/bevy_render/src/lib.rs | 109 +++++++++++++--------------------- 1 file changed, 42 insertions(+), 67 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 2385b530817e1..35ff2ca6c3551 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -22,6 +22,7 @@ mod spatial_bundle; pub mod texture; pub mod view; +use bevy_derive::{Deref, DerefMut}; use bevy_hierarchy::ValidParentCheckPlugin; pub use extract_param::Extract; @@ -52,7 +53,7 @@ use crate::{ }; use bevy_app::{App, AppLabel, CoreSchedule, Plugin}; use bevy_asset::{AddAsset, AssetServer}; -use bevy_ecs::{prelude::*, system::SystemState}; +use bevy_ecs::{prelude::*, schedule::ScheduleLabel, system::SystemState}; use bevy_utils::tracing::debug; use std::{ any::TypeId, @@ -70,11 +71,6 @@ pub struct RenderPlugin { /// The sets run in the order listed, with [`apply_system_buffers`] inserted between each set. #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum RenderSet { - /// Extract data from the "app world" and insert it into the "render world". - /// This step should be kept as short as possible to increase the "pipelining potential" for - /// running the next frame while rendering the current frame. - Extract, - /// A stage for applying the commands from the [`Extract`] stage ExtractCommands, @@ -106,14 +102,7 @@ impl RenderSet { pub fn base_schedule() -> Schedule { let mut schedule = Schedule::new(); - // Don't apply buffers when the Extract stage finishes running - // This runs on the render world, but buffers are applied - // after access to the main world is removed - // See also https://github.com/bevyengine/bevy/issues/5082 - schedule.configure_set(RenderSet::Extract.before(RenderSet::ExtractCommands)); - - // Buffer flushes + induced ordering - + // Create "stage-like" structure using buffer flushes + induced ordering schedule.add_system( apply_system_buffers .after(RenderSet::ExtractCommands) @@ -145,9 +134,25 @@ impl RenderSet { } } -/// Resource for holding the extract stage of the rendering schedule. -#[derive(Resource)] -pub struct ExtractStage(pub SystemStage); +/// The [`ScheduleLabel`] for [`ExtractSchedule`]. +#[derive(ScheduleLabel, PartialEq, Eq, Debug, Clone, Hash)] +struct RenderExtraction; + +/// Schedule which extract data from the main world and inserts it into the render world. +/// +/// This step should be kept as short as possible to increase the "pipelining potential" for +/// running the next frame while rendering the current frame. +/// +/// This schedule is run on the main world, but its buffers are not applied +/// via [`Shedule::apply_system_buffers`] until it is returned to the render world. +/// +/// This schedule is stored as a resource on the render world, +/// which is mutable accessed from the main world via the borrow splitting enabled by [`App::add_subapp`]. +/// +/// When adding systems to this schedule, be careful not to add any copies of [`apply_system_buffers`], +/// as that system will attempt to extract the data into the main world pre-emptively! +#[derive(Resource, Deref, DerefMut, Default)] +pub struct ExtractSchedule(pub Schedule); /// The simulation [`World`] of the application, stored as a resource. /// This resource is only available during [`RenderStage::Extract`] and not @@ -220,8 +225,7 @@ impl Plugin for RenderPlugin { app.insert_resource(device.clone()) .insert_resource(queue.clone()) .insert_resource(adapter_info.clone()) - .insert_resource(render_adapter.clone()) - .init_resource::(); + .insert_resource(render_adapter.clone()); let pipeline_cache = PipelineCache::new(device.clone()); let asset_server = app.world.resource::().clone(); @@ -229,7 +233,10 @@ impl Plugin for RenderPlugin { let mut render_app = App::empty(); let mut render_schedule = RenderSet::base_schedule(); - render_schedule.add_system(PipelineCache::extract_shaders.in_set(RenderSet::Extract)); + // Prepare the schedule which extracts data from the main world to the render world + let extract_schedule = ExtractSchedule::default(); + extract_schedule.add_system(PipelineCache::extract_shaders); + // Get the ComponentId for MainWorld. This does technically 'waste' a `WorldId`, but that's probably fine render_app.init_resource::(); render_app.world.remove_resource::(); @@ -266,7 +273,7 @@ impl Plugin for RenderPlugin { app.insert_resource(receiver); render_app.insert_resource(sender); - app.add_sub_app(RenderApp, render_app, move |app_world, render_app| { + app.add_sub_app(RenderApp, render_app, move |main_world, render_app| { #[cfg(feature = "trace")] let _render_span = bevy_utils::tracing::info_span!("extract main app to render subapp").entered(); { @@ -275,9 +282,9 @@ impl Plugin for RenderPlugin { bevy_utils::tracing::info_span!("stage", name = "reserve_and_flush") .entered(); - // reserve all existing app entities for use in render_app + // reserve all existing main world entities for use in render_app // they can only be spawned using `get_or_spawn()` - let total_count = app_world.entities().total_count(); + let total_count = main_world.entities().total_count(); assert_eq!( render_app.world.entities().len(), @@ -299,8 +306,14 @@ impl Plugin for RenderPlugin { let _stage_span = bevy_utils::tracing::info_span!("stage", name = "extract").entered(); - // extract - extract(app_world, render_app); + // Fetch the extract schedule from the render world to the main world + let extract_schedule = render_app.world.resource_mut::(); + + // Run the extract schedule on the main world, being careful not to apply buffers + extract_schedule.run(main_world); + + // Now that the data is safely captured in a resource on the render world, + // we can release the lock on the main world, allowing its schedule to proceed. } }); } @@ -317,49 +330,11 @@ impl Plugin for RenderPlugin { .register_type::() .register_type::(); } - - fn setup(&self, app: &mut App) { - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - // move the extract stage to a resource so render_app.run() does not run it. - let stage = render_app - .schedule - .remove_stage(RenderSet::Extract) - .unwrap() - .downcast::() - .unwrap(); - - render_app.world.insert_resource(ExtractStage(*stage)); - } - } -} - -/// A "scratch" world used to avoid allocating new worlds every frame when -/// swapping out the [`MainWorld`] for [`RenderStage::Extract`]. -#[derive(Resource, Default)] -struct ScratchMainWorld(World); - -/// Executes the [`Extract`](RenderStage::Extract) stage of the renderer. -/// This updates the render world with the extracted ECS data of the current frame. -fn extract(app_world: &mut World, render_app: &mut App) { - render_app - .world - .resource_scope(|render_world, mut extract_stage: Mut| { - // temporarily add the app world to the render world as a resource - let scratch_world = app_world.remove_resource::().unwrap(); - let inserted_world = std::mem::replace(app_world, scratch_world.0); - render_world.insert_resource(MainWorld(inserted_world)); - - extract_stage.0.run(render_world); - // move the app world back, as if nothing happened. - let inserted_world = render_world.remove_resource::().unwrap(); - let scratch_world = std::mem::replace(app_world, inserted_world.0); - app_world.insert_resource(ScratchMainWorld(scratch_world)); - }); } -// system for render app to apply the extract commands -fn apply_extract_commands(world: &mut World) { - world.resource_scope(|world, mut extract_stage: Mut| { - extract_stage.0.apply_buffers(world); +/// Applies the +fn apply_extract_commands(render_world: &mut World) { + render_world.resource_scope(|render_world, extract_schedule: Mut| { + extract_schedule.apply_system_buffers(render_world); }); } From 63b587a75845d83bf8c9682d6a494d72563bcc14 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 20 Jan 2023 18:06:03 -0500 Subject: [PATCH 082/247] Add and use App::add_extract_system convenience method --- crates/bevy_core_pipeline/src/core_2d/mod.rs | 3 ++- crates/bevy_core_pipeline/src/core_3d/mod.rs | 5 +++-- crates/bevy_pbr/src/lib.rs | 16 ++++++---------- crates/bevy_pbr/src/material.rs | 3 ++- crates/bevy_pbr/src/render/mesh.rs | 4 ++-- crates/bevy_pbr/src/wireframe.rs | 5 +++-- crates/bevy_render/src/camera/mod.rs | 8 +++----- crates/bevy_render/src/extract_component.rs | 12 +++++------- crates/bevy_render/src/extract_resource.rs | 6 ++---- crates/bevy_render/src/globals.rs | 9 +++++---- crates/bevy_render/src/lib.rs | 19 +++++++++++++++++++ crates/bevy_render/src/render_asset.rs | 5 +++-- crates/bevy_render/src/view/window.rs | 5 +++-- crates/bevy_sprite/src/lib.rs | 9 +++------ crates/bevy_sprite/src/mesh2d/material.rs | 4 +++- crates/bevy_sprite/src/mesh2d/mesh.rs | 4 ++-- crates/bevy_text/src/lib.rs | 9 +-------- crates/bevy_ui/src/render/mod.rs | 17 +++++------------ examples/2d/mesh2d_manual.rs | 3 ++- examples/stress_tests/many_lights.rs | 5 ++--- 20 files changed, 76 insertions(+), 75 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 073bfc0dc8e6e..efd5bedd4b71d 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -44,6 +44,8 @@ impl Plugin for Core2dPlugin { app.register_type::() .add_plugin(ExtractComponentPlugin::::default()); + app.add_extract_system(extract_core_2d_camera_phases); + let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, Err(_) => return, @@ -51,7 +53,6 @@ impl Plugin for Core2dPlugin { render_app .init_resource::>() - .add_system(extract_core_2d_camera_phases.in_set(RenderSet::Extract)) .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) .add_system( batch_phase_system:: diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index ee7537228641d..8671860215a2b 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -40,7 +40,7 @@ use bevy_render::{ renderer::RenderDevice, texture::TextureCache, view::ViewDepthTexture, - Extract, RenderApp, RenderSet, + Extract, RenderApp, RenderSet, RenderingAppExtension, }; use bevy_utils::{FloatOrd, HashMap}; @@ -58,6 +58,8 @@ impl Plugin for Core3dPlugin { .register_type::() .add_plugin(ExtractComponentPlugin::::default()); + app.add_extract_system(extract_core_3d_camera_phases); + let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, Err(_) => return, @@ -67,7 +69,6 @@ impl Plugin for Core3dPlugin { .init_resource::>() .init_resource::>() .init_resource::>() - .add_system(extract_core_3d_camera_phases.in_set(RenderSet::Extract)) .add_system(prepare_core_3d_depth_textures.in_set(RenderSet::Prepare)) .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index f5c456ed50672..b2eca6e39ced6 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -232,22 +232,18 @@ impl Plugin for PbrPlugin { }, ); + // Extract the required data from the main world + app.add_extract_system( + render::extract_clusters.in_set(RenderLightSystems::ExtractClusters), + ) + .add_extract_system(render::extract_lights.in_set(RenderLightSystems::ExtractLights)); + let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, Err(_) => return, }; render_app - .add_system( - render::extract_clusters - .in_set(RenderLightSystems::ExtractClusters) - .in_set(RenderSet::Extract), - ) - .add_system( - render::extract_lights - .in_set(RenderLightSystems::ExtractLights) - .in_set(RenderSet::Extract), - ) .add_system( // this is added as an exclusive system because it contributes new views. it must run (and have Commands applied) // _before_ the `prepare_views()` system is run. ideally this becomes a normal system when "stageless" features come out diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 5b49d63964d1e..e1d6d50b3cf9f 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -189,6 +189,8 @@ where app.add_asset::() .add_plugin(ExtractComponentPlugin::>::extract_visible()); + app.add_extract_system(extract_materials::); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .add_render_command::>() @@ -198,7 +200,6 @@ where .init_resource::>() .init_resource::>() .init_resource::>>() - .add_system(extract_materials::.in_set(RenderSet::Extract)) .add_system( prepare_materials:: .after(PrepareAssetLabel::PreAssetPrepare) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index da2a450aea435..6930c4b873ae7 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -96,13 +96,13 @@ impl Plugin for MeshRenderPlugin { load_internal_asset!(app, SKINNING_HANDLE, "skinning.wgsl", Shader::from_wgsl); app.add_plugin(UniformComponentPlugin::::default()); + app.add_extract_system(extract_meshes); + app.add_extract_system(extract_skinned_meshes); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() .init_resource::() - .add_system(extract_meshes.in_set(RenderSet::Extract)) - .add_system(extract_skinned_meshes.in_set(RenderSet::Extract)) .add_system(prepare_skinned_meshes.in_set(RenderSet::Prepare)) .add_system(queue_mesh_bind_group.in_set(RenderSet::Queue)) .add_system(queue_mesh_view_bind_groups.in_set(RenderSet::Queue)); diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 34305b57584f0..625a7c11e83c8 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -6,7 +6,6 @@ use bevy_core_pipeline::core_3d::Opaque3d; use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::{Reflect, TypeUuid}; -use bevy_render::Extract; use bevy_render::{ extract_resource::{ExtractResource, ExtractResourcePlugin}, mesh::{Mesh, MeshVertexBufferLayout}, @@ -19,6 +18,7 @@ use bevy_render::{ view::{ExtractedView, Msaa, VisibleEntities}, RenderApp, RenderSet, }; +use bevy_render::{Extract, RenderingAppExtension}; use bevy_utils::tracing::error; pub const WIREFRAME_SHADER_HANDLE: HandleUntyped = @@ -41,12 +41,13 @@ impl Plugin for WireframePlugin { .init_resource::() .add_plugin(ExtractResourcePlugin::::default()); + app.add_extract_system(extract_wireframes); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .add_render_command::() .init_resource::() .init_resource::>() - .add_system(extract_wireframes.in_set(RenderSet::Extract)) .add_system(queue_wireframes.in_set(RenderSet::Queue)); } } diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index 26a3b2ae86995..8c6c4bd4116f1 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -7,9 +7,8 @@ pub use camera::*; pub use camera_driver_node::*; pub use projection::*; -use crate::{render_graph::RenderGraph, RenderApp, RenderSet}; +use crate::{render_graph::RenderGraph, RenderApp, RenderingAppExtension}; use bevy_app::{App, Plugin}; -use bevy_ecs::prelude::*; #[derive(Default)] pub struct CameraPlugin; @@ -25,11 +24,10 @@ impl Plugin for CameraPlugin { .register_type::() .add_plugin(CameraProjectionPlugin::::default()) .add_plugin(CameraProjectionPlugin::::default()) - .add_plugin(CameraProjectionPlugin::::default()); + .add_plugin(CameraProjectionPlugin::::default()) + .add_extract_system(extract_cameras); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_system(extract_cameras.in_set(RenderSet::Extract)); - let camera_driver_node = CameraDriverNode::new(&mut render_app.world); let mut render_graph = render_app.world.resource_mut::(); render_graph.add_node(crate::main_graph::node::CAMERA_DRIVER, camera_driver_node); diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index d0d8284e66a34..7f5d28265dbc7 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -2,7 +2,7 @@ use crate::{ render_resource::{encase::internal::WriteInto, DynamicUniformBuffer, ShaderType}, renderer::{RenderDevice, RenderQueue}, view::ComputedVisibility, - Extract, RenderApp, RenderSet, + Extract, RenderApp, RenderSet, RenderingAppExtension, }; use bevy_app::{App, Plugin}; use bevy_asset::{Asset, Handle}; @@ -176,12 +176,10 @@ impl ExtractComponentPlugin { impl Plugin for ExtractComponentPlugin { fn build(&self, app: &mut App) { - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - if self.only_extract_visible { - render_app.add_system(extract_visible_components::.in_set(RenderSet::Extract)); - } else { - render_app.add_system(extract_components::.in_set(RenderSet::Extract)); - } + if self.only_extract_visible { + app.add_extract_system(extract_visible_components::); + } else { + app.add_extract_system(extract_components::); } } } diff --git a/crates/bevy_render/src/extract_resource.rs b/crates/bevy_render/src/extract_resource.rs index c899b06d4d788..e42ecb47de29d 100644 --- a/crates/bevy_render/src/extract_resource.rs +++ b/crates/bevy_render/src/extract_resource.rs @@ -5,7 +5,7 @@ use bevy_ecs::prelude::*; #[cfg(debug_assertions)] pub use bevy_render_macros::ExtractResource; -use crate::{Extract, RenderApp, RenderSet}; +use crate::{Extract, RenderingAppExtension}; /// Describes how a resource gets extracted for rendering. /// @@ -32,9 +32,7 @@ impl Default for ExtractResourcePlugin { impl Plugin for ExtractResourcePlugin { fn build(&self, app: &mut App) { - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_system(extract_resource::.in_set(RenderSet::Extract)); - } + app.add_extract_system(extract_resource::); } } diff --git a/crates/bevy_render/src/globals.rs b/crates/bevy_render/src/globals.rs index c9f3164d5ebe3..cff79f8b129af 100644 --- a/crates/bevy_render/src/globals.rs +++ b/crates/bevy_render/src/globals.rs @@ -2,7 +2,7 @@ use crate::{ extract_resource::ExtractResource, render_resource::{ShaderType, UniformBuffer}, renderer::{RenderDevice, RenderQueue}, - Extract, RenderApp, RenderSet, + Extract, RenderApp, RenderSet, RenderingAppExtension, }; use bevy_app::{App, Plugin}; use bevy_core::FrameCount; @@ -14,13 +14,14 @@ pub struct GlobalsPlugin; impl Plugin for GlobalsPlugin { fn build(&self, app: &mut App) { - app.register_type::(); + app.register_type::() + .add_extract_system(extract_frame_count) + .add_extract_system(extract_time); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() .init_resource::

(&mut self, system: impl IntoSystemConfig

) -> &mut Self; +} + +impl RenderingAppExtension for App { + fn add_extract_system

(&mut self, system: impl IntoSystemConfig

) -> &mut Self { + let rendering_subapp = self.get_sub_app_mut(RenderApp).unwrap(); + let extract_schedule = rendering_subapp.world.resource_mut::(); + extract_schedule.add_system(system); + self + } +} diff --git a/crates/bevy_render/src/render_asset.rs b/crates/bevy_render/src/render_asset.rs index 2fd372e5a6acb..203c6eab1fcb0 100644 --- a/crates/bevy_render/src/render_asset.rs +++ b/crates/bevy_render/src/render_asset.rs @@ -1,4 +1,4 @@ -use crate::{Extract, RenderApp, RenderSet}; +use crate::{Extract, RenderApp, RenderSet, RenderingAppExtension}; use bevy_app::{App, Plugin}; use bevy_asset::{Asset, AssetEvent, Assets, Handle}; use bevy_derive::{Deref, DerefMut}; @@ -78,6 +78,8 @@ impl Default for RenderAssetPlugin { impl Plugin for RenderAssetPlugin { fn build(&self, app: &mut App) { + app.add_extract_system(extract_render_asset::); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { let prepare_asset_system = prepare_assets::.in_set(self.prepare_asset_label.clone()); @@ -95,7 +97,6 @@ impl Plugin for RenderAssetPlugin { .init_resource::>() .init_resource::>() .init_resource::>() - .add_system(extract_render_asset::.in_set(RenderSet::Extract)) .add_system(prepare_asset_system.in_set(RenderSet::Prepare)); } } diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 9b3086ce80840..bdb760f67d99a 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -1,7 +1,7 @@ use crate::{ render_resource::TextureView, renderer::{RenderAdapter, RenderDevice, RenderInstance}, - Extract, RenderApp, RenderSet, + Extract, RenderApp, RenderSet, RenderingAppExtension, }; use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; @@ -25,12 +25,13 @@ pub enum WindowSystem { impl Plugin for WindowRenderPlugin { fn build(&self, app: &mut App) { + app.add_extract_system(extract_windows); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() .init_resource::() .init_non_send_resource::() - .add_system(extract_windows.in_set(RenderSet::Extract)) .add_system( prepare_windows .in_set(WindowSystem::Prepare) diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index a79a88789cdb3..c1904e6d587fc 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -61,6 +61,9 @@ impl Plugin for SpritePlugin { .add_plugin(Mesh2dRenderPlugin) .add_plugin(ColorMaterialPlugin); + app.add_extract_system(extract_sprites.in_set(SpriteSystem::ExtractSprites)); + app.add_extract_system(extract_sprite_events); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() @@ -70,12 +73,6 @@ impl Plugin for SpritePlugin { .init_resource::() .init_resource::() .add_render_command::() - .add_system( - render::extract_sprites - .in_set(SpriteSystem::ExtractSprites) - .in_set(RenderSet::Extract), - ) - .add_system(render::extract_sprite_events.in_set(RenderSet::Extract)) .add_system(queue_sprites.in_set(RenderSet::Queue)); }; } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 886a1ddf33c6b..fabad4e2364c4 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -153,6 +153,9 @@ where fn build(&self, app: &mut App) { app.add_asset::() .add_plugin(ExtractComponentPlugin::>::extract_visible()); + + app.add_extract_system(extract_materials_2d::); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .add_render_command::>() @@ -160,7 +163,6 @@ where .init_resource::>() .init_resource::>() .init_resource::>>() - .add_system(extract_materials_2d::.in_set(RenderSet::Extract)) .add_system( prepare_materials_2d:: .after(PrepareAssetLabel::PreAssetPrepare) diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index a8a4dbc2d0bab..3854e51e839fc 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -21,7 +21,7 @@ use bevy_render::{ view::{ ComputedVisibility, ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, }, - Extract, RenderApp, RenderSet, + Extract, RenderApp, RenderSet, RenderingAppExtension, }; use bevy_transform::components::GlobalTransform; @@ -97,12 +97,12 @@ impl Plugin for Mesh2dRenderPlugin { load_internal_asset!(app, MESH2D_SHADER_HANDLE, "mesh2d.wgsl", Shader::from_wgsl); app.add_plugin(UniformComponentPlugin::::default()); + app.add_extract_system(extract_mesh2d); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() .init_resource::>() - .add_system(extract_mesh2d.in_set(RenderSet::Extract)) .add_system(queue_mesh2d_bind_group.in_set(RenderSet::Queue)) .add_system(queue_mesh2d_view_bind_groups.in_set(RenderSet::Queue)); } diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 823cf7111a3d6..03e3dd0cc9686 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -80,6 +80,7 @@ impl Plugin for TextPlugin { .init_resource::() .init_resource::() .insert_resource(TextPipeline::default()) + .add_extract_system(extract_text2d_sprite.after(SpriteSystem::ExtractSprites)) .add_system( update_text2d_layout .in_set(CoreSet::PostUpdate) @@ -90,13 +91,5 @@ impl Plugin for TextPlugin { // will never modify a pre-existing `Image` asset. .ambiguous_with(CameraUpdateSystem), ); - - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_system( - extract_text2d_sprite - .after(SpriteSystem::ExtractSprites) - .in_set(RenderSet::Extract), - ); - } } } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 04fa9e7605f81..012291e45fb24 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -58,6 +58,11 @@ pub enum RenderUiSystem { pub fn build_ui_render(app: &mut App) { load_internal_asset!(app, UI_SHADER_HANDLE, "ui.wgsl", Shader::from_wgsl); + app.add_extract_system(extract_default_ui_camera_view::) + .add_extract_system(extract_default_ui_camera_view::) + .add_extract_system(extract_uinodes.in_set(RenderUiSystem::ExtractNode)) + .add_extract_system(extract_text_uinodes.after(RenderUiSystem::ExtractNode)); + let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, Err(_) => return, @@ -71,18 +76,6 @@ pub fn build_ui_render(app: &mut App) { .init_resource::() .init_resource::>() .add_render_command::() - .add_system(extract_default_ui_camera_view::.in_set(RenderSet::Extract)) - .add_system(extract_default_ui_camera_view::.in_set(RenderSet::Extract)) - .add_system( - extract_uinodes - .in_set(RenderUiSystem::ExtractNode) - .in_set(RenderSet::Extract), - ) - .add_system( - extract_text_uinodes - .after(RenderUiSystem::ExtractNode) - .in_set(RenderSet::Extract), - ) .add_system(prepare_uinodes.in_set(RenderSet::Prepare)) .add_system(queue_uinodes.in_set(RenderSet::Queue)) .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)); diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 247457fce11fd..01725c9cb92ab 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -276,13 +276,14 @@ impl Plugin for ColoredMesh2dPlugin { Shader::from_wgsl(COLORED_MESH2D_SHADER), ); + app.add_extract_system(extract_colored_mesh2d); + // Register our custom draw function and pipeline, and add our render systems app.get_sub_app_mut(RenderApp) .unwrap() .add_render_command::() .init_resource::() .init_resource::>() - .add_system_to_stage(RenderSet::Extract, extract_colored_mesh2d) .add_system_to_stage(RenderSet::Queue, queue_colored_mesh2d); } } diff --git a/examples/stress_tests/many_lights.rs b/examples/stress_tests/many_lights.rs index 62d816651b4ac..088486287722a 100644 --- a/examples/stress_tests/many_lights.rs +++ b/examples/stress_tests/many_lights.rs @@ -153,14 +153,13 @@ struct LogVisibleLights; impl Plugin for LogVisibleLights { fn build(&self, app: &mut App) { + app.add_extract_system(extract_time); let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, Err(_) => return, }; - render_app - .add_system(extract_time.in_set(RenderSet::Extract)) - .add_system(print_visible_light_count.in_set(RenderSet::Prepare)); + render_app.add_system(print_visible_light_count.in_set(RenderSet::Prepare)); } } From d6330ec9b9c668e211cea36c0c0029fc26297860 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 20 Jan 2023 18:13:24 -0500 Subject: [PATCH 083/247] Re-add MainThreadExecutor --- crates/bevy_ecs/src/schedule/executor/mod.rs | 2 +- .../src/schedule/executor/multi_threaded.rs | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/executor/mod.rs b/crates/bevy_ecs/src/schedule/executor/mod.rs index c4b894c3ad146..bfd35497d7eb6 100644 --- a/crates/bevy_ecs/src/schedule/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule/executor/mod.rs @@ -2,7 +2,7 @@ mod multi_threaded; mod simple; mod single_threaded; -pub use self::multi_threaded::MultiThreadedExecutor; +pub use self::multi_threaded::{MainThreadExecutor, MultiThreadedExecutor}; pub use self::simple::SimpleExecutor; pub use self::single_threaded::SingleThreadedExecutor; diff --git a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs index 69e083767c65e..f26e369ce5fef 100644 --- a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs @@ -1,4 +1,6 @@ -use bevy_tasks::{ComputeTaskPool, Scope, TaskPool}; +use std::sync::Arc; + +use bevy_tasks::{ComputeTaskPool, Scope, TaskPool, ThreadExecutor}; use bevy_utils::default; use bevy_utils::syncunsafecell::SyncUnsafeCell; #[cfg(feature = "trace")] @@ -9,6 +11,7 @@ use fixedbitset::FixedBitSet; use crate::{ archetype::ArchetypeComponentId, + prelude::Resource, query::Access, schedule::{ is_apply_system_buffers, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule, @@ -17,6 +20,8 @@ use crate::{ world::World, }; +use crate as bevy_ecs; + /// A funky borrow split of [`SystemSchedule`] required by the [`MultiThreadedExecutor`]. struct SyncUnsafeSchedule<'a> { systems: &'a [SyncUnsafeCell], @@ -573,3 +578,13 @@ fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &World }) .fold(true, |acc, res| acc && res) } + +/// New-typed [`ThreadExecutor`] [`Resource`] that is used to run systems on the main thread +#[derive(Resource, Default, Clone)] +pub struct MainThreadExecutor(pub Arc>); + +impl MainThreadExecutor { + pub fn new() -> Self { + MainThreadExecutor(Arc::new(ThreadExecutor::new())) + } +} From 49f6221abf89e77803dd8d5e17f68731aaf56c7c Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 20 Jan 2023 18:15:09 -0500 Subject: [PATCH 084/247] Fix merge conflict --- crates/bevy_winit/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 2dae3d90e636a..681b55d719f84 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -12,7 +12,6 @@ pub use winit_config::*; pub use winit_windows::*; use bevy_app::{App, AppExit, CoreSet, Plugin}; -use bevy_app::{App, AppExit, CoreStage, Plugin}; use bevy_ecs::event::{Events, ManualEventReader}; use bevy_ecs::prelude::*; use bevy_input::{ From 0718073894f56c937f7fddaa22b074ca61213fd1 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 20 Jan 2023 18:20:16 -0500 Subject: [PATCH 085/247] Move calculate_bounds system to Update --- crates/bevy_render/src/view/visibility/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 1c906f1c83003..b8f1103e63a0d 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -213,8 +213,8 @@ impl Plugin for VisibilityPlugin { app.add_system( calculate_bounds .in_set(CalculateBounds) - .before_commands() - .in_set(CoreSet::PostUpdate), + // The commands from this system must be applied before we compute culling frustra + .in_set(CoreSet::Update), ) .add_system( update_frusta:: From 525679f3d37bfc30fb7772f80985d5e202373fbc Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 20 Jan 2023 18:34:00 -0500 Subject: [PATCH 086/247] Add Schedule::apply_system_buffers --- crates/bevy_ecs/src/schedule/schedule.rs | 16 ++++++++++++++++ crates/bevy_render/src/lib.rs | 3 --- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 54beac1915647..94437ca47e6aa 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -230,6 +230,22 @@ impl Schedule { } } } + + /// Directly applies any accumulated system buffers (like [`Commands`](crate::prelude::Commands)) to the `world`. + /// + /// Like always, system buffers are applied in the "topological sort order" of the schedule graph. + /// As a result, buffers from one system are only guaranteed to be applied before those of other systems + /// if there is an explicit system ordering between the two systems. + /// + /// This is used in rendering to extract data from the main world, storing the data in system buffers, + /// before applying their buffers in a different world. + pub fn apply_system_buffers(&mut self, world: &mut World) { + for system in &mut self.executable.systems { + #[cfg(feature = "trace")] + let _apply_buffers_span = info_span!("apply_buffers", name = &*system.name()).entered(); + system.apply_buffers(world); + } + } } /// A directed acylic graph structure. diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 9b58dc6b966d2..8f0213a97498f 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -149,9 +149,6 @@ struct RenderExtraction; /// /// This schedule is stored as a resource on the render world, /// which is mutable accessed from the main world via the borrow splitting enabled by [`App::add_subapp`]. -/// -/// When adding systems to this schedule, be careful not to add any copies of [`apply_system_buffers`], -/// as that system will attempt to extract the data into the main world pre-emptively! #[derive(Resource, Deref, DerefMut, Default)] pub struct ExtractSchedule(pub Schedule); From d82e5339e7f631b0815e7a0b59f9384932e2bd21 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 20 Jan 2023 18:37:18 -0500 Subject: [PATCH 087/247] Fix new systems added in bevy_winit --- crates/bevy_winit/src/lib.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 681b55d719f84..6878f4155e0d8 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -50,12 +50,10 @@ impl Plugin for WinitPlugin { app.init_non_send_resource::() .init_resource::() .set_runner(winit_runner) - .add_system_set_to_stage( - CoreStage::PostUpdate, - SystemSet::new() - .label(ModifiesWindows) - .with_system(changed_window) - .with_system(despawn_window), + .add_systems( + (changed_window, despawn_window) + .in_set(ModifiesWindows) + .in_set(CoreSet::PostUpdate), ); #[cfg(target_arch = "wasm32")] From 021141c3a6cc14ee495e3fc5d84a71f3d3158729 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 20 Jan 2023 18:41:44 -0500 Subject: [PATCH 088/247] Remove dead imports to unbreak RA --- crates/bevy_render/src/pipelined_rendering.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_render/src/pipelined_rendering.rs b/crates/bevy_render/src/pipelined_rendering.rs index 63c0a0cfb74b6..d5e3d01333ac6 100644 --- a/crates/bevy_render/src/pipelined_rendering.rs +++ b/crates/bevy_render/src/pipelined_rendering.rs @@ -2,7 +2,7 @@ use async_channel::{Receiver, Sender}; use bevy_app::{App, AppLabel, Plugin, SubApp}; use bevy_ecs::{ - schedule::{MainThreadExecutor, StageLabel, SystemStage}, + schedule::MainThreadExecutor, system::Resource, world::{Mut, World}, }; @@ -15,7 +15,7 @@ use crate::RenderApp; pub struct RenderExtractApp; /// Labels for stages in the [`RenderExtractApp`] sub app. These will run after rendering has started. -#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] +#[derive(Debug, Hash, PartialEq, Eq, Clone)] pub enum RenderExtractStage { /// When pipelined rendering is enabled this stage runs after the render schedule starts, but /// before I/O processing and the main app schedule. This can be useful for something like From de49e4e47e88bbb5f63b5dec399e298be4739b93 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Fri, 20 Jan 2023 18:43:45 -0800 Subject: [PATCH 089/247] get bevy_render compiling --- crates/bevy_render/src/lib.rs | 42 +++++++++++-------- crates/bevy_render/src/pipelined_rendering.rs | 32 +++++--------- 2 files changed, 36 insertions(+), 38 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 8f0213a97498f..8e2b1bb95844a 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -57,7 +57,6 @@ use bevy_asset::{AddAsset, AssetServer}; use bevy_ecs::{prelude::*, schedule::ScheduleLabel, system::SystemState}; use bevy_utils::tracing::debug; use std::{ - any::TypeId, ops::{Deref, DerefMut}, }; @@ -159,9 +158,10 @@ pub struct ExtractSchedule(pub Schedule); #[derive(Resource, Default)] pub struct MainWorld(World); -/// The Render App World. This is only available as a resource during the Extract step. +/// A "scratch" world used to avoid allocating new worlds every frame when +/// swapping out the [`MainWorld`] for [`RenderStage::Extract`]. #[derive(Resource, Default)] -pub struct RenderWorld(World); +struct ScratchMainWorld(World); impl Deref for MainWorld { type Target = World; @@ -223,7 +223,8 @@ impl Plugin for RenderPlugin { app.insert_resource(device.clone()) .insert_resource(queue.clone()) .insert_resource(adapter_info.clone()) - .insert_resource(render_adapter.clone()); + .insert_resource(render_adapter.clone()) + .init_resource::(); let pipeline_cache = PipelineCache::new(device.clone()); let asset_server = app.world.resource::().clone(); @@ -232,16 +233,13 @@ impl Plugin for RenderPlugin { let mut render_schedule = RenderSet::base_schedule(); // Prepare the schedule which extracts data from the main world to the render world - let extract_schedule = ExtractSchedule::default(); + let mut extract_schedule = ExtractSchedule::default(); extract_schedule.add_system(PipelineCache::extract_shaders); + render_app.insert_resource(extract_schedule); // Get the ComponentId for MainWorld. This does technically 'waste' a `WorldId`, but that's probably fine render_app.init_resource::(); render_app.world.remove_resource::(); - let main_world_in_render = render_app - .world - .components() - .get_resource_id(TypeId::of::()); // This set applies the commands from the extract stage while the render schedule // is running in parallel with the main app. @@ -299,19 +297,29 @@ impl Plugin for RenderPlugin { } } + // run extract schedule { #[cfg(feature = "trace")] let _stage_span = bevy_utils::tracing::info_span!("stage", name = "extract").entered(); - // Fetch the extract schedule from the render world to the main world - let extract_schedule = render_app.world.resource_mut::(); - // Run the extract schedule on the main world, being careful not to apply buffers - extract_schedule.run(main_world); + render_app + .world + .resource_scope(|render_world, mut extract_schedule: Mut| { + + // temporarily add the app world to the render world as a resource + let scratch_world = main_world.remove_resource::().unwrap(); + let inserted_world = std::mem::replace(main_world, scratch_world.0); + render_world.insert_resource(MainWorld(inserted_world)); - // Now that the data is safely captured in a resource on the render world, - // we can release the lock on the main world, allowing its schedule to proceed. + extract_schedule.run(render_world); + + // move the app world back, as if nothing happened. + let inserted_world = render_world.remove_resource::().unwrap(); + let scratch_world = std::mem::replace(main_world, inserted_world.0); + main_world.insert_resource(ScratchMainWorld(scratch_world)); + }); } }); } @@ -332,7 +340,7 @@ impl Plugin for RenderPlugin { /// Applies the fn apply_extract_commands(render_world: &mut World) { - render_world.resource_scope(|render_world, extract_schedule: Mut| { + render_world.resource_scope(|render_world, mut extract_schedule: Mut| { extract_schedule.apply_system_buffers(render_world); }); } @@ -349,7 +357,7 @@ pub trait RenderingAppExtension { impl RenderingAppExtension for App { fn add_extract_system

(&mut self, system: impl IntoSystemConfig

) -> &mut Self { let rendering_subapp = self.get_sub_app_mut(RenderApp).unwrap(); - let extract_schedule = rendering_subapp.world.resource_mut::(); + let mut extract_schedule = rendering_subapp.world.resource_mut::(); extract_schedule.add_system(system); self } diff --git a/crates/bevy_render/src/pipelined_rendering.rs b/crates/bevy_render/src/pipelined_rendering.rs index d5e3d01333ac6..eecff73ab09f5 100644 --- a/crates/bevy_render/src/pipelined_rendering.rs +++ b/crates/bevy_render/src/pipelined_rendering.rs @@ -11,18 +11,12 @@ use bevy_tasks::ComputeTaskPool; use crate::RenderApp; /// A Label for the sub app that runs the parts of pipelined rendering that need to run on the main thread. +/// +/// The Main schedule of this app can be used to run logic after the render schedule starts, but +/// before I/O processing. This can be useful for something like frame pacing. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] pub struct RenderExtractApp; -/// Labels for stages in the [`RenderExtractApp`] sub app. These will run after rendering has started. -#[derive(Debug, Hash, PartialEq, Eq, Clone)] -pub enum RenderExtractStage { - /// When pipelined rendering is enabled this stage runs after the render schedule starts, but - /// before I/O processing and the main app schedule. This can be useful for something like - /// frame pacing. - BeforeIoAfterRenderStart, -} - /// Channel to send the render app from the main thread to the rendering thread #[derive(Resource)] pub struct MainToRenderAppSender(pub Sender); @@ -49,11 +43,11 @@ pub struct RenderToMainAppReceiver(pub Receiver); /// A single frame of execution looks something like below /// /// ```text -/// |-------------------------------------------------------------------| -/// | | BeforeIoAfterRenderStart | winit events | main schedule | -/// | extract |---------------------------------------------------------| -/// | | extract commands | rendering schedule | -/// |-------------------------------------------------------------------| +/// |--------------------------------------------------------------------| +/// | | RenderExtractApp schedule | winit events | main schedule | +/// | extract |----------------------------------------------------------| +/// | | extract commands | rendering schedule | +/// |--------------------------------------------------------------------| /// ``` /// /// - `extract` is the stage where data is copied from the main world to the render world. @@ -61,8 +55,8 @@ pub struct RenderToMainAppReceiver(pub Receiver); /// - On the render thread, we first apply the `extract commands`. This is not run during extract, so the /// main schedule can start sooner. /// - Then the `rendering schedule` is run. See [`crate::RenderStage`] for the available stages. -/// - In parallel to the rendering thread we first run the [`RenderExtractStage::BeforeIoAfterRenderStart`] stage. By -/// default this stage is empty. But is useful if you need something to run before I/O processing. +/// - In parallel to the rendering thread the [`RenderExtractApp`] schedule runs. By +/// default this schedule is empty. But it is useful if you need something to run before I/O processing. /// - Next all the `winit events` are processed. /// - And finally the `main app schedule` is run. /// - Once both the `main app schedule` and the `render schedule` are finished running, `extract` is run again. @@ -77,11 +71,7 @@ impl Plugin for PipelinedRenderingPlugin { } app.insert_resource(MainThreadExecutor::new()); - let mut sub_app = App::empty(); - sub_app.add_stage( - RenderExtractStage::BeforeIoAfterRenderStart, - SystemStage::parallel(), - ); + let sub_app = App::empty(); app.add_sub_app(RenderExtractApp, sub_app, update_rendering); } From d5c8224c3f7396bf96974e4de7975576f664337a Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Fri, 20 Jan 2023 19:19:35 -0800 Subject: [PATCH 090/247] move some stuff back to make the diff cleaner --- crates/bevy_render/src/lib.rs | 56 +++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 8e2b1bb95844a..2acc8cfe1391a 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -56,9 +56,7 @@ use bevy_app::{App, AppLabel, CoreSchedule, Plugin}; use bevy_asset::{AddAsset, AssetServer}; use bevy_ecs::{prelude::*, schedule::ScheduleLabel, system::SystemState}; use bevy_utils::tracing::debug; -use std::{ - ops::{Deref, DerefMut}, -}; +use std::ops::{Deref, DerefMut}; /// Contains the default Bevy rendering backend based on wgpu. #[derive(Default)] @@ -158,11 +156,6 @@ pub struct ExtractSchedule(pub Schedule); #[derive(Resource, Default)] pub struct MainWorld(World); -/// A "scratch" world used to avoid allocating new worlds every frame when -/// swapping out the [`MainWorld`] for [`RenderStage::Extract`]. -#[derive(Resource, Default)] -struct ScratchMainWorld(World); - impl Deref for MainWorld { type Target = World; @@ -303,23 +296,7 @@ impl Plugin for RenderPlugin { let _stage_span = bevy_utils::tracing::info_span!("stage", name = "extract").entered(); - - render_app - .world - .resource_scope(|render_world, mut extract_schedule: Mut| { - - // temporarily add the app world to the render world as a resource - let scratch_world = main_world.remove_resource::().unwrap(); - let inserted_world = std::mem::replace(main_world, scratch_world.0); - render_world.insert_resource(MainWorld(inserted_world)); - - extract_schedule.run(render_world); - - // move the app world back, as if nothing happened. - let inserted_world = render_world.remove_resource::().unwrap(); - let scratch_world = std::mem::replace(main_world, inserted_world.0); - main_world.insert_resource(ScratchMainWorld(scratch_world)); - }); + extract(main_world, render_app); } }); } @@ -338,7 +315,34 @@ impl Plugin for RenderPlugin { } } -/// Applies the +/// A "scratch" world used to avoid allocating new worlds every frame when +/// swapping out the [`MainWorld`] for [`RenderStage::Extract`]. +#[derive(Resource, Default)] +struct ScratchMainWorld(World); + +/// Executes the [`Extract`](RenderStage::Extract) stage of the renderer. +/// This updates the render world with the extracted ECS data of the current frame. +fn extract(main_world: &mut World, render_app: &mut App) { + render_app + .world + .resource_scope(|render_world, mut extract_schedule: Mut| { + // temporarily add the app world to the render world as a resource + let scratch_world = main_world.remove_resource::().unwrap(); + let inserted_world = std::mem::replace(main_world, scratch_world.0); + render_world.insert_resource(MainWorld(inserted_world)); + + extract_schedule.run(render_world); + + // move the app world back, as if nothing happened. + let inserted_world = render_world.remove_resource::().unwrap(); + let scratch_world = std::mem::replace(main_world, inserted_world.0); + main_world.insert_resource(ScratchMainWorld(scratch_world)); + }); +} + +/// Applies the commands from the extract schedule. This happens during +/// the render schedule rather than during extraction to allow the commands to run in parallel with the +/// main app when pipelined rendering is enabled. fn apply_extract_commands(render_world: &mut World) { render_world.resource_scope(|render_world, mut extract_schedule: Mut| { extract_schedule.apply_system_buffers(render_world); From 7c36bf9208b88dde682592bb7bb63058c3da589e Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Fri, 20 Jan 2023 20:26:24 -0800 Subject: [PATCH 091/247] add main thread executor to multithreaded executor scope --- .../src/schedule/executor/multi_threaded.rs | 86 ++++++++++--------- 1 file changed, 46 insertions(+), 40 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs index f26e369ce5fef..089cb14ce5a9b 100644 --- a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs @@ -156,53 +156,59 @@ impl SystemExecutor for MultiThreadedExecutor { mut conditions, } = SyncUnsafeSchedule::new(schedule); - ComputeTaskPool::init(TaskPool::default).scope(|scope| { - // the executor itself is a `Send` future so that it can run - // alongside systems that claim the local thread - let executor = async { - while self.num_completed_systems < num_systems { - // SAFETY: self.ready_systems does not contain running systems - unsafe { - self.spawn_system_tasks(scope, systems, &mut conditions, world); - } - - if self.num_running_systems > 0 { - // wait for systems to complete - let index = self - .receiver - .recv() - .await - .unwrap_or_else(|error| unreachable!("{}", error)); + let thread_executor = world.get_resource::().map(|e| &*e.0); + + ComputeTaskPool::init(TaskPool::default).scope_with_executor( + false, + thread_executor, + |scope| { + // the executor itself is a `Send` future so that it can run + // alongside systems that claim the local thread + let executor = async { + while self.num_completed_systems < num_systems { + // SAFETY: self.ready_systems does not contain running systems + unsafe { + self.spawn_system_tasks(scope, systems, &mut conditions, world); + } - self.finish_system_and_signal_dependents(index); + if self.num_running_systems > 0 { + // wait for systems to complete + let index = self + .receiver + .recv() + .await + .unwrap_or_else(|error| unreachable!("{}", error)); - while let Ok(index) = self.receiver.try_recv() { self.finish_system_and_signal_dependents(index); - } - self.rebuild_active_access(); + while let Ok(index) = self.receiver.try_recv() { + self.finish_system_and_signal_dependents(index); + } + + self.rebuild_active_access(); + } } - } - // SAFETY: all systems have completed - let world = unsafe { &mut *world.get() }; - apply_system_buffers(&mut self.unapplied_systems, systems, world); - - debug_assert!(self.ready_systems.is_clear()); - debug_assert!(self.running_systems.is_clear()); - debug_assert!(self.unapplied_systems.is_clear()); - self.active_access.clear(); - self.evaluated_sets.clear(); - self.skipped_systems.clear(); - self.completed_systems.clear(); - }; + // SAFETY: all systems have completed + let world = unsafe { &mut *world.get() }; + apply_system_buffers(&mut self.unapplied_systems, systems, world); - #[cfg(feature = "trace")] - let executor_span = info_span!("schedule_task"); - #[cfg(feature = "trace")] - let executor = executor.instrument(executor_span); - scope.spawn(executor); - }); + debug_assert!(self.ready_systems.is_clear()); + debug_assert!(self.running_systems.is_clear()); + debug_assert!(self.unapplied_systems.is_clear()); + self.active_access.clear(); + self.evaluated_sets.clear(); + self.skipped_systems.clear(); + self.completed_systems.clear(); + }; + + #[cfg(feature = "trace")] + let executor_span = info_span!("schedule_task"); + #[cfg(feature = "trace")] + let executor = executor.instrument(executor_span); + scope.spawn(executor); + }, + ); } } From 1103e1678b5f2f70f693d027803ec272bfdf3ed1 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Sun, 22 Jan 2023 14:00:35 -0800 Subject: [PATCH 092/247] just have extract schedule live in render app schedules --- crates/bevy_core_pipeline/src/core_2d/mod.rs | 7 +- crates/bevy_core_pipeline/src/core_3d/mod.rs | 7 +- crates/bevy_pbr/src/lib.rs | 16 +++-- crates/bevy_pbr/src/material.rs | 7 +- crates/bevy_pbr/src/render/mesh.rs | 9 ++- crates/bevy_pbr/src/wireframe.rs | 9 +-- crates/bevy_render/src/camera/mod.rs | 9 ++- crates/bevy_render/src/extract_component.rs | 14 ++-- crates/bevy_render/src/extract_resource.rs | 8 ++- crates/bevy_render/src/globals.rs | 11 ++-- crates/bevy_render/src/lib.rs | 67 ++++++++------------ crates/bevy_render/src/render_asset.rs | 7 +- crates/bevy_render/src/view/window.rs | 7 +- crates/bevy_sprite/src/lib.rs | 8 ++- crates/bevy_sprite/src/mesh2d/material.rs | 5 +- crates/bevy_sprite/src/mesh2d/mesh.rs | 4 +- crates/bevy_text/src/lib.rs | 8 ++- crates/bevy_ui/src/render/mod.rs | 13 ++-- examples/2d/mesh2d_manual.rs | 7 +- examples/stress_tests/many_lights.rs | 7 +- 20 files changed, 129 insertions(+), 101 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index efd5bedd4b71d..684a1bfde02b1 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -30,7 +30,7 @@ use bevy_render::{ DrawFunctionId, DrawFunctions, PhaseItem, RenderPhase, }, render_resource::CachedRenderPipelineId, - Extract, RenderApp, RenderSet, + Extract, ExtractSchedule, RenderApp, RenderSet, }; use bevy_utils::FloatOrd; use std::ops::Range; @@ -44,8 +44,6 @@ impl Plugin for Core2dPlugin { app.register_type::() .add_plugin(ExtractComponentPlugin::::default()); - app.add_extract_system(extract_core_2d_camera_phases); - let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, Err(_) => return, @@ -53,6 +51,9 @@ impl Plugin for Core2dPlugin { render_app .init_resource::>() + .edit_schedule(&ExtractSchedule, |extract_schedule| { + extract_schedule.add_system(extract_core_2d_camera_phases); + }) .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) .add_system( batch_phase_system:: diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 8671860215a2b..054c505d69e4e 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -40,7 +40,7 @@ use bevy_render::{ renderer::RenderDevice, texture::TextureCache, view::ViewDepthTexture, - Extract, RenderApp, RenderSet, RenderingAppExtension, + Extract, ExtractSchedule, RenderApp, RenderSet, }; use bevy_utils::{FloatOrd, HashMap}; @@ -58,8 +58,6 @@ impl Plugin for Core3dPlugin { .register_type::() .add_plugin(ExtractComponentPlugin::::default()); - app.add_extract_system(extract_core_3d_camera_phases); - let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, Err(_) => return, @@ -69,6 +67,9 @@ impl Plugin for Core3dPlugin { .init_resource::>() .init_resource::>() .init_resource::>() + .edit_schedule(&ExtractSchedule, |extract_schedule| { + extract_schedule.add_system(extract_core_3d_camera_phases); + }) .add_system(prepare_core_3d_depth_textures.in_set(RenderSet::Prepare)) .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index b2eca6e39ced6..97aff927b9e24 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -52,7 +52,7 @@ use bevy_render::{ render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions}, render_resource::{Shader, SpecializedMeshPipelines}, view::VisibilitySystems, - RenderApp, RenderSet, + ExtractSchedule, RenderApp, RenderSet, }; use bevy_transform::TransformSystem; @@ -232,18 +232,20 @@ impl Plugin for PbrPlugin { }, ); - // Extract the required data from the main world - app.add_extract_system( - render::extract_clusters.in_set(RenderLightSystems::ExtractClusters), - ) - .add_extract_system(render::extract_lights.in_set(RenderLightSystems::ExtractLights)); - let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, Err(_) => return, }; + // Extract the required data from the main world render_app + .edit_schedule(&ExtractSchedule, |extract_schedule| { + extract_schedule + .add_system( + render::extract_clusters.in_set(RenderLightSystems::ExtractClusters), + ) + .add_system(render::extract_lights.in_set(RenderLightSystems::ExtractLights)); + }) .add_system( // this is added as an exclusive system because it contributes new views. it must run (and have Commands applied) // _before_ the `prepare_views()` system is run. ideally this becomes a normal system when "stageless" features come out diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index e1d6d50b3cf9f..f52aa9be4dcc6 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -37,7 +37,7 @@ use bevy_render::{ renderer::RenderDevice, texture::FallbackImage, view::{ExtractedView, Msaa, VisibleEntities}, - Extract, RenderApp, RenderSet, + Extract, ExtractSchedule, RenderApp, RenderSet, }; use bevy_utils::{tracing::error, HashMap, HashSet}; use std::hash::Hash; @@ -189,8 +189,6 @@ where app.add_asset::() .add_plugin(ExtractComponentPlugin::>::extract_visible()); - app.add_extract_system(extract_materials::); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .add_render_command::>() @@ -200,6 +198,9 @@ where .init_resource::>() .init_resource::>() .init_resource::>>() + .edit_schedule(&ExtractSchedule, |extract_schedule| { + extract_schedule.add_system(extract_materials::); + }) .add_system( prepare_materials:: .after(PrepareAssetLabel::PreAssetPrepare) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 6930c4b873ae7..d6ea7d3e48ab6 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -30,7 +30,7 @@ use bevy_render::{ ImageSampler, TextureFormatPixelInfo, }, view::{ComputedVisibility, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms}, - Extract, RenderApp, RenderSet, + Extract, ExtractSchedule, RenderApp, RenderSet, }; use bevy_transform::components::GlobalTransform; use std::num::NonZeroU64; @@ -96,13 +96,16 @@ impl Plugin for MeshRenderPlugin { load_internal_asset!(app, SKINNING_HANDLE, "skinning.wgsl", Shader::from_wgsl); app.add_plugin(UniformComponentPlugin::::default()); - app.add_extract_system(extract_meshes); - app.add_extract_system(extract_skinned_meshes); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() .init_resource::() + .edit_schedule(&ExtractSchedule, |extract_schedule| { + extract_schedule + .add_extract_system(extract_meshes) + .add_extract_system(extract_skinned_meshes); + }) .add_system(prepare_skinned_meshes.in_set(RenderSet::Prepare)) .add_system(queue_mesh_bind_group.in_set(RenderSet::Queue)) .add_system(queue_mesh_view_bind_groups.in_set(RenderSet::Queue)); diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 625a7c11e83c8..62ecbb91d3289 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -16,9 +16,9 @@ use bevy_render::{ SpecializedMeshPipelineError, SpecializedMeshPipelines, }, view::{ExtractedView, Msaa, VisibleEntities}, - RenderApp, RenderSet, + ExtractSchedule, RenderApp, RenderSet, }; -use bevy_render::{Extract, RenderingAppExtension}; +use bevy_render::{Extract, ExtractSchedule, RenderingAppExtension}; use bevy_utils::tracing::error; pub const WIREFRAME_SHADER_HANDLE: HandleUntyped = @@ -41,13 +41,14 @@ impl Plugin for WireframePlugin { .init_resource::() .add_plugin(ExtractResourcePlugin::::default()); - app.add_extract_system(extract_wireframes); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .add_render_command::() .init_resource::() .init_resource::>() + .edit_schedule(&ExtractSchedule, |extract_schedule| { + extract_schedule.add_system(extract_wireframes); + }) .add_system(queue_wireframes.in_set(RenderSet::Queue)); } } diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index 8c6c4bd4116f1..5de84fc7a7533 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -7,7 +7,7 @@ pub use camera::*; pub use camera_driver_node::*; pub use projection::*; -use crate::{render_graph::RenderGraph, RenderApp, RenderingAppExtension}; +use crate::{render_graph::RenderGraph, ExtractSchedule, RenderApp}; use bevy_app::{App, Plugin}; #[derive(Default)] @@ -24,10 +24,13 @@ impl Plugin for CameraPlugin { .register_type::() .add_plugin(CameraProjectionPlugin::::default()) .add_plugin(CameraProjectionPlugin::::default()) - .add_plugin(CameraProjectionPlugin::::default()) - .add_extract_system(extract_cameras); + .add_plugin(CameraProjectionPlugin::::default()); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.edit_schedule(&ExtractSchedule, |extract_schedule| { + extract_schedule.add_system(extract_cameras); + }); + let camera_driver_node = CameraDriverNode::new(&mut render_app.world); let mut render_graph = render_app.world.resource_mut::(); render_graph.add_node(crate::main_graph::node::CAMERA_DRIVER, camera_driver_node); diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index 7f5d28265dbc7..da2592479fe89 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -2,7 +2,7 @@ use crate::{ render_resource::{encase::internal::WriteInto, DynamicUniformBuffer, ShaderType}, renderer::{RenderDevice, RenderQueue}, view::ComputedVisibility, - Extract, RenderApp, RenderSet, RenderingAppExtension, + Extract, ExtractSchedule, RenderApp, RenderSet, }; use bevy_app::{App, Plugin}; use bevy_asset::{Asset, Handle}; @@ -176,10 +176,14 @@ impl ExtractComponentPlugin { impl Plugin for ExtractComponentPlugin { fn build(&self, app: &mut App) { - if self.only_extract_visible { - app.add_extract_system(extract_visible_components::); - } else { - app.add_extract_system(extract_components::); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.edit_schedule(&ExtractSchedule, |extract_schedule| { + if self.only_extract_visible { + extract_schedule.add_system(extract_visible_components::); + } else { + extract_schedule.add_system(extract_components::); + } + }); } } } diff --git a/crates/bevy_render/src/extract_resource.rs b/crates/bevy_render/src/extract_resource.rs index e42ecb47de29d..2aa6bd176b5fa 100644 --- a/crates/bevy_render/src/extract_resource.rs +++ b/crates/bevy_render/src/extract_resource.rs @@ -5,7 +5,7 @@ use bevy_ecs::prelude::*; #[cfg(debug_assertions)] pub use bevy_render_macros::ExtractResource; -use crate::{Extract, RenderingAppExtension}; +use crate::{Extract, ExtractSchedule, RenderApp}; /// Describes how a resource gets extracted for rendering. /// @@ -32,7 +32,11 @@ impl Default for ExtractResourcePlugin { impl Plugin for ExtractResourcePlugin { fn build(&self, app: &mut App) { - app.add_extract_system(extract_resource::); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.edit_schedule(&ExtractSchedule, |extract_schedule| { + extract_schedule.add_system(extract_resource::); + }); + } } } diff --git a/crates/bevy_render/src/globals.rs b/crates/bevy_render/src/globals.rs index cff79f8b129af..c379275a13983 100644 --- a/crates/bevy_render/src/globals.rs +++ b/crates/bevy_render/src/globals.rs @@ -2,7 +2,7 @@ use crate::{ extract_resource::ExtractResource, render_resource::{ShaderType, UniformBuffer}, renderer::{RenderDevice, RenderQueue}, - Extract, RenderApp, RenderSet, RenderingAppExtension, + Extract, ExtractSchedule, RenderApp, RenderSet, }; use bevy_app::{App, Plugin}; use bevy_core::FrameCount; @@ -14,14 +14,17 @@ pub struct GlobalsPlugin; impl Plugin for GlobalsPlugin { fn build(&self, app: &mut App) { - app.register_type::() - .add_extract_system(extract_frame_count) - .add_extract_system(extract_time); + app.register_type::(); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() .init_resource::

(&mut self, system: impl IntoSystemConfig

) -> &mut Self; -} - -impl RenderingAppExtension for App { - fn add_extract_system

(&mut self, system: impl IntoSystemConfig

) -> &mut Self { - let rendering_subapp = self.get_sub_app_mut(RenderApp).unwrap(); - let mut extract_schedule = rendering_subapp.world.resource_mut::(); - extract_schedule.add_system(system); - self - } -} diff --git a/crates/bevy_render/src/render_asset.rs b/crates/bevy_render/src/render_asset.rs index 203c6eab1fcb0..b041ce80fb5b3 100644 --- a/crates/bevy_render/src/render_asset.rs +++ b/crates/bevy_render/src/render_asset.rs @@ -1,4 +1,4 @@ -use crate::{Extract, RenderApp, RenderSet, RenderingAppExtension}; +use crate::{Extract, ExtractSchedule, RenderApp, RenderSet}; use bevy_app::{App, Plugin}; use bevy_asset::{Asset, AssetEvent, Assets, Handle}; use bevy_derive::{Deref, DerefMut}; @@ -78,8 +78,6 @@ impl Default for RenderAssetPlugin { impl Plugin for RenderAssetPlugin { fn build(&self, app: &mut App) { - app.add_extract_system(extract_render_asset::); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { let prepare_asset_system = prepare_assets::.in_set(self.prepare_asset_label.clone()); @@ -97,6 +95,9 @@ impl Plugin for RenderAssetPlugin { .init_resource::>() .init_resource::>() .init_resource::>() + .edit_schedule(&ExtractSchedule, |extract_schedule| { + extract_schedule.add_system(extract_render_asset::); + }) .add_system(prepare_asset_system.in_set(RenderSet::Prepare)); } } diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index bdb760f67d99a..ae7fc6b383433 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -1,7 +1,7 @@ use crate::{ render_resource::TextureView, renderer::{RenderAdapter, RenderDevice, RenderInstance}, - Extract, RenderApp, RenderSet, RenderingAppExtension, + Extract, ExtractSchedule, RenderApp, RenderSet, }; use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; @@ -25,13 +25,14 @@ pub enum WindowSystem { impl Plugin for WindowRenderPlugin { fn build(&self, app: &mut App) { - app.add_extract_system(extract_windows); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() .init_resource::() .init_non_send_resource::() + .edit_schedule(&ExtractSchedule, |extract_schedule| { + extract_schedule.add_system(extract_windows); + }) .add_system( prepare_windows .in_set(WindowSystem::Prepare) diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index c1904e6d587fc..fde4a5bf3d7c3 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -61,9 +61,6 @@ impl Plugin for SpritePlugin { .add_plugin(Mesh2dRenderPlugin) .add_plugin(ColorMaterialPlugin); - app.add_extract_system(extract_sprites.in_set(SpriteSystem::ExtractSprites)); - app.add_extract_system(extract_sprite_events); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() @@ -73,6 +70,11 @@ impl Plugin for SpritePlugin { .init_resource::() .init_resource::() .add_render_command::() + .edit_schedule(&ExtractSchedule, |extract_schedule| { + extract_schedule + .add_system(extract_sprites.in_set(SpriteSystem::ExtractSprites)) + .add_system(extract_sprite_events); + }) .add_system(queue_sprites.in_set(RenderSet::Queue)); }; } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index fabad4e2364c4..5949eb89599a4 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -154,8 +154,6 @@ where app.add_asset::() .add_plugin(ExtractComponentPlugin::>::extract_visible()); - app.add_extract_system(extract_materials_2d::); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .add_render_command::>() @@ -163,6 +161,9 @@ where .init_resource::>() .init_resource::>() .init_resource::>>() + .edit_schedule(&ExtractSchedule, |extract_schedule| { + extract_schedule.add_system(extract_materials_2d::); + }) .add_system( prepare_materials_2d:: .after(PrepareAssetLabel::PreAssetPrepare) diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 3854e51e839fc..b5538957de967 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -97,12 +97,14 @@ impl Plugin for Mesh2dRenderPlugin { load_internal_asset!(app, MESH2D_SHADER_HANDLE, "mesh2d.wgsl", Shader::from_wgsl); app.add_plugin(UniformComponentPlugin::::default()); - app.add_extract_system(extract_mesh2d); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() .init_resource::>() + .edit_schedule(&ExtractSchedule, |extract_schedule| { + extract_schedule.add_system(extract_mesh2d); + }) .add_system(queue_mesh2d_bind_group.in_set(RenderSet::Queue)) .add_system(queue_mesh2d_view_bind_groups.in_set(RenderSet::Queue)); } diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 03e3dd0cc9686..9a75f07fed2a2 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -80,7 +80,6 @@ impl Plugin for TextPlugin { .init_resource::() .init_resource::() .insert_resource(TextPipeline::default()) - .add_extract_system(extract_text2d_sprite.after(SpriteSystem::ExtractSprites)) .add_system( update_text2d_layout .in_set(CoreSet::PostUpdate) @@ -91,5 +90,12 @@ impl Plugin for TextPlugin { // will never modify a pre-existing `Image` asset. .ambiguous_with(CameraUpdateSystem), ); + + if let Some(render_app) = app.get_sub_app_mut(RenderLabel) { + render_app.edit_schedule(&ExtractSchedule, |extract_schedule| { + extract_schedule + .add_system(extract_text2d_sprite.after(SpriteSystem::ExtractSprites)); + }) + } } } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 012291e45fb24..613d47bffc634 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -2,6 +2,7 @@ mod pipeline; mod render_pass; use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; +use bevy_render::ExtractSchedule; use bevy_window::{PrimaryWindow, Window}; pub use pipeline::*; pub use render_pass::*; @@ -58,11 +59,6 @@ pub enum RenderUiSystem { pub fn build_ui_render(app: &mut App) { load_internal_asset!(app, UI_SHADER_HANDLE, "ui.wgsl", Shader::from_wgsl); - app.add_extract_system(extract_default_ui_camera_view::) - .add_extract_system(extract_default_ui_camera_view::) - .add_extract_system(extract_uinodes.in_set(RenderUiSystem::ExtractNode)) - .add_extract_system(extract_text_uinodes.after(RenderUiSystem::ExtractNode)); - let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, Err(_) => return, @@ -76,6 +72,13 @@ pub fn build_ui_render(app: &mut App) { .init_resource::() .init_resource::>() .add_render_command::() + .edit_schedule(&ExtractSchedule, |extract_schedule| { + extract_schedule + .add_system(extract_default_ui_camera_view::) + .add_system(extract_default_ui_camera_view::) + .add_system(extract_uinodes.in_set(RenderUiSystem::ExtractNode)) + .add_system(extract_text_uinodes.after(RenderUiSystem::ExtractNode)); + }) .add_system(prepare_uinodes.in_set(RenderSet::Prepare)) .add_system(queue_uinodes.in_set(RenderSet::Queue)) .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)); diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 01725c9cb92ab..ecdae234f807c 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -276,15 +276,16 @@ impl Plugin for ColoredMesh2dPlugin { Shader::from_wgsl(COLORED_MESH2D_SHADER), ); - app.add_extract_system(extract_colored_mesh2d); - // Register our custom draw function and pipeline, and add our render systems app.get_sub_app_mut(RenderApp) .unwrap() .add_render_command::() .init_resource::() .init_resource::>() - .add_system_to_stage(RenderSet::Queue, queue_colored_mesh2d); + .edit_schedule(&ExtractSchedule, |extract_schedule| { + extract_schedule.add_system(extract_colored_mesh2d); + }) + .add_system(queue_colored_mesh2d.in_set(RenderSet::Queue)); } } diff --git a/examples/stress_tests/many_lights.rs b/examples/stress_tests/many_lights.rs index 088486287722a..be17b9dd3c122 100644 --- a/examples/stress_tests/many_lights.rs +++ b/examples/stress_tests/many_lights.rs @@ -153,13 +153,16 @@ struct LogVisibleLights; impl Plugin for LogVisibleLights { fn build(&self, app: &mut App) { - app.add_extract_system(extract_time); let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, Err(_) => return, }; - render_app.add_system(print_visible_light_count.in_set(RenderSet::Prepare)); + render_app + .edit_schedule(&ExtractSchedule, |extract_schedule| { + extract_schedule.add_system(extract_time); + }) + .add_system(print_visible_light_count.in_set(RenderSet::Prepare)); } } From 29e3c3a081e2f1c106a6a34ce7f913a99079e834 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Sun, 22 Jan 2023 16:12:32 -0800 Subject: [PATCH 093/247] fix getting thread executor from the world --- crates/bevy_ecs/src/schedule/executor/multi_threaded.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs index 089cb14ce5a9b..6789922befb64 100644 --- a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs @@ -150,14 +150,17 @@ impl SystemExecutor for MultiThreadedExecutor { } } + let thread_executor = world + .get_resource::() + .map(|e| e.0.clone()); + let thread_executor = thread_executor.as_deref(); + let world = SyncUnsafeCell::from_mut(world); let SyncUnsafeSchedule { systems, mut conditions, } = SyncUnsafeSchedule::new(schedule); - let thread_executor = world.get_resource::().map(|e| &*e.0); - ComputeTaskPool::init(TaskPool::default).scope_with_executor( false, thread_executor, From ee3046143653d87c8494bbca8c9c86135954d5ea Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Mon, 23 Jan 2023 10:15:44 +0100 Subject: [PATCH 094/247] fix default_schedule_label lookup --- crates/bevy_app/src/app.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 81bc0f299bbe6..7ab7741e4cbb0 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -339,7 +339,7 @@ impl App { pub fn add_system

(&mut self, system: impl IntoSystemConfig

) -> &mut Self { let mut schedules = self.world.resource_mut::(); - if let Some(default_schedule) = schedules.get_mut(&self.default_schedule_label) { + if let Some(default_schedule) = schedules.get_mut(&*self.default_schedule_label) { default_schedule.add_system(system); } else { let schedule_label = &self.default_schedule_label; @@ -367,7 +367,7 @@ impl App { pub fn add_systems

(&mut self, systems: impl IntoSystemConfigs

) -> &mut Self { let mut schedules = self.world.resource_mut::(); - if let Some(default_schedule) = schedules.get_mut(&self.default_schedule_label) { + if let Some(default_schedule) = schedules.get_mut(&*self.default_schedule_label) { default_schedule.add_systems(systems); } else { let schedule_label = &self.default_schedule_label; From 405569d3c75404c26c31e1bfc81841f39aa80a7c Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 23 Jan 2023 10:10:13 -0500 Subject: [PATCH 095/247] Move scene_spawner_system --- crates/bevy_scene/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 0ab55bfddd16c..8256b2fc77545 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -36,7 +36,7 @@ impl Plugin for ScenePlugin { .add_asset::() .init_asset_loader::() .init_resource::() - .add_system(scene_spawner_system.at_end().in_set(CoreSet::PreUpdate)) + .add_system(scene_spawner_system.in_set(CoreSet::Update)) // Systems `*_bundle_spawner` must run before `scene_spawner_system` .add_system(scene_spawner.in_set(CoreSet::PreUpdate)); } From f4fc8398c2ef0028c3a0a7765e4ebd029fcd0100 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 23 Jan 2023 10:20:09 -0500 Subject: [PATCH 096/247] Trivial compiler error fixes --- crates/bevy_core_pipeline/src/bloom/mod.rs | 6 ++---- crates/bevy_core_pipeline/src/core_2d/mod.rs | 3 ++- crates/bevy_pbr/src/lib.rs | 2 +- crates/bevy_pbr/src/material.rs | 7 ++----- crates/bevy_pbr/src/prepass/mod.rs | 2 +- crates/bevy_pbr/src/wireframe.rs | 2 +- crates/bevy_render/src/lib.rs | 1 - crates/bevy_sprite/src/lib.rs | 4 ++-- crates/bevy_sprite/src/mesh2d/material.rs | 9 +++------ crates/bevy_sprite/src/mesh2d/mesh.rs | 2 +- 10 files changed, 15 insertions(+), 23 deletions(-) diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index bec9eedc0b394..416c64e7ed636 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -2,10 +2,8 @@ use crate::{core_2d, core_3d, fullscreen_vertex_shader::fullscreen_shader_vertex use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, HandleUntyped}; use bevy_ecs::{ - prelude::{Component, Entity}, - query::{QueryItem, QueryState, With}, - system::{Commands, Query, Res, ResMut, Resource}, - world::{FromWorld, World}, + prelude::*, + query::{QueryItem, QueryState}, }; use bevy_math::{UVec2, UVec4, Vec4}; use bevy_reflect::{Reflect, TypeUuid}; diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 684a1bfde02b1..e4ac71f420206 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -57,7 +57,8 @@ impl Plugin for Core2dPlugin { .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) .add_system( batch_phase_system:: - .after(sort_phase_system::.in_set(RenderSet::PhaseSort)), + .after(sort_phase_system::) + .in_set(RenderSet::PhaseSort), ); let pass_node_2d = MainPass2dNode::new(&mut render_app.world); diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 97aff927b9e24..59dace45fa96b 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -198,7 +198,7 @@ impl Plugin for PbrPlugin { .add_system( update_point_light_frusta .in_set(SimulationLightSystems::UpdateLightFrusta) - .lable(CoreSet::PostUpdate) + .in_set(CoreSet::PostUpdate) .after(TransformSystem::TransformPropagate) .after(SimulationLightSystems::AssignLightsToClusters), ) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 5d0176c5aa978..ef6ebfcc66123 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -10,14 +10,11 @@ use bevy_core_pipeline::{ }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - event::EventReader, - prelude::World, - schedule::IntoSystemDescriptor, + prelude::*, system::{ lifetimeless::{Read, SRes}, - Commands, Local, Query, Res, ResMut, Resource, SystemParamItem, + SystemParamItem, }, - world::FromWorld, }; use bevy_reflect::TypeUuid; use bevy_render::{ diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 8202409d4aa99..525d96fe479d6 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -39,7 +39,7 @@ use bevy_render::{ renderer::RenderDevice, texture::TextureCache, view::{ExtractedView, Msaa, ViewUniform, ViewUniformOffset, ViewUniforms, VisibleEntities}, - Extract, RenderApp, RenderStage, + Extract, RenderApp, }; use bevy_utils::{tracing::error, HashMap}; diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 62ecbb91d3289..1b6d0af516ccf 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -6,6 +6,7 @@ use bevy_core_pipeline::core_3d::Opaque3d; use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::{Reflect, TypeUuid}; +use bevy_render::Extract; use bevy_render::{ extract_resource::{ExtractResource, ExtractResourcePlugin}, mesh::{Mesh, MeshVertexBufferLayout}, @@ -18,7 +19,6 @@ use bevy_render::{ view::{ExtractedView, Msaa, VisibleEntities}, ExtractSchedule, RenderApp, RenderSet, }; -use bevy_render::{Extract, ExtractSchedule, RenderingAppExtension}; use bevy_utils::tracing::error; pub const WIREFRAME_SHADER_HANDLE: HandleUntyped = diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index f3e69569cd37a..03f7c8ac5b651 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -22,7 +22,6 @@ mod spatial_bundle; pub mod texture; pub mod view; -use bevy_derive::{Deref, DerefMut}; use bevy_hierarchy::ValidParentCheckPlugin; pub use extract_param::Extract; diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index fde4a5bf3d7c3..7dcfe8c5c915d 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -29,12 +29,12 @@ pub use texture_atlas_builder::*; use bevy_app::prelude::*; use bevy_asset::{AddAsset, Assets, HandleUntyped}; use bevy_core_pipeline::core_2d::Transparent2d; -use bevy_ecs::schedule::{IntoSystemDescriptor, SystemSet}; +use bevy_ecs::prelude::*; use bevy_reflect::TypeUuid; use bevy_render::{ render_phase::AddRenderCommand, render_resource::{Shader, SpecializedRenderPipelines}, - RenderApp, RenderSet, + ExtractSchedule, RenderApp, RenderSet, }; #[derive(Default)] diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 5949eb89599a4..ef3e0b2b83051 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -3,15 +3,12 @@ use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle}; use bevy_core_pipeline::{core_2d::Transparent2d, tonemapping::Tonemapping}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - event::EventReader, - prelude::{Bundle, World}, + prelude::*, query::ROQueryItem, - schedule::IntoSystemDescriptor, system::{ lifetimeless::{Read, SRes}, - Commands, Local, Query, Res, ResMut, Resource, SystemParamItem, + SystemParamItem, }, - world::FromWorld, }; use bevy_log::error; use bevy_reflect::TypeUuid; @@ -32,7 +29,7 @@ use bevy_render::{ renderer::RenderDevice, texture::FallbackImage, view::{ComputedVisibility, ExtractedView, Msaa, Visibility, VisibleEntities}, - Extract, RenderApp, RenderSet, + Extract, ExtractSchedule, RenderApp, RenderSet, }; use bevy_transform::components::{GlobalTransform, Transform}; use bevy_utils::{FloatOrd, HashMap, HashSet}; diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index b5538957de967..9fb8fe66a2a5c 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -21,7 +21,7 @@ use bevy_render::{ view::{ ComputedVisibility, ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, }, - Extract, RenderApp, RenderSet, RenderingAppExtension, + Extract, ExtractSchedule, RenderApp, RenderSet, RenderingAppExtension, }; use bevy_transform::components::GlobalTransform; From 99f5367d7c10addbdd223fb3653da4a1e5b8e5b8 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 23 Jan 2023 10:27:34 -0500 Subject: [PATCH 097/247] Migrate more rendering systems --- crates/bevy_pbr/src/prepass/mod.rs | 25 +++++++++++-------------- crates/bevy_pbr/src/render/mesh.rs | 4 ++-- crates/bevy_sprite/src/mesh2d/mesh.rs | 2 +- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 525d96fe479d6..69e572c49c355 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -8,13 +8,11 @@ use bevy_core_pipeline::{ }, }; use bevy_ecs::{ - prelude::Entity, - query::With, + prelude::*, system::{ lifetimeless::{Read, SRes}, - Commands, Query, Res, ResMut, Resource, SystemParamItem, + SystemParamItem, }, - world::{FromWorld, World}, }; use bevy_reflect::TypeUuid; use bevy_render::{ @@ -39,7 +37,7 @@ use bevy_render::{ renderer::RenderDevice, texture::TextureCache, view::{ExtractedView, Msaa, ViewUniform, ViewUniformOffset, ViewUniforms, VisibleEntities}, - Extract, RenderApp, + Extract, ExtractSchedule, RenderApp, RenderSet, }; use bevy_utils::{tracing::error, HashMap}; @@ -89,15 +87,14 @@ where }; render_app - .add_system_to_stage(RenderStage::Extract, extract_camera_prepass_phase) - .add_system_to_stage(RenderStage::Prepare, prepare_prepass_textures) - .add_system_to_stage(RenderStage::Queue, queue_prepass_view_bind_group::) - .add_system_to_stage(RenderStage::Queue, queue_prepass_material_meshes::) - .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) - .add_system_to_stage( - RenderStage::PhaseSort, - sort_phase_system::, - ) + .edit_schedule(&ExtractSchedule, |extract_schedule| { + extract_schedule.add_system(extract_camera_prepass_phase); + }) + .add_system(prepare_prepass_textures.in_set(RenderSet::Prepare)) + .add_system(queue_prepass_view_bind_group::.in_set(RenderSet::Queue)) + .add_system(queue_prepass_material_meshes::.in_set(RenderSet::Queue)) + .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) + .add_system(sort_phase_system::.in_set(RenderSet::PhaseSort)) .init_resource::>() .init_resource::>() .init_resource::>() diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 834a5a94554f6..a2c5d62f8eab1 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -103,8 +103,8 @@ impl Plugin for MeshRenderPlugin { .init_resource::() .edit_schedule(&ExtractSchedule, |extract_schedule| { extract_schedule - .add_extract_system(extract_meshes) - .add_extract_system(extract_skinned_meshes); + .add_system(extract_meshes) + .add_system(extract_skinned_meshes); }) .add_system(prepare_skinned_meshes.in_set(RenderSet::Prepare)) .add_system(queue_mesh_bind_group.in_set(RenderSet::Queue)) diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 9fb8fe66a2a5c..e58386d3bc67c 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -21,7 +21,7 @@ use bevy_render::{ view::{ ComputedVisibility, ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, }, - Extract, ExtractSchedule, RenderApp, RenderSet, RenderingAppExtension, + Extract, ExtractSchedule, RenderApp, RenderSet, }; use bevy_transform::components::GlobalTransform; From 27d738f8aa07b891de6a89dc647d9ae5fe27ad98 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 23 Jan 2023 10:30:55 -0500 Subject: [PATCH 098/247] Make bevy_pbr::add_clusters an ordinary system --- crates/bevy_pbr/src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 59dace45fa96b..cdfde8764343b 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -167,12 +167,10 @@ impl Plugin for PbrPlugin { .init_resource::() .add_plugin(ExtractResourcePlugin::::default()) .add_system( - // NOTE: Clusters need to have been added before update_clusters is run so - // add as an exclusive system add_clusters - .at_start() .in_set(SimulationLightSystems::AddClusters) - .in_set(CoreSet::PostUpdate), + .in_set(CoreSet::PostUpdate) + .before(assign_lights_to_clusters), ) .add_system( assign_lights_to_clusters From dcef89c1900c85a99def93f96db4206c8c86cfb8 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 23 Jan 2023 10:33:25 -0500 Subject: [PATCH 099/247] Get bevy_text compiling --- crates/bevy_text/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 9a75f07fed2a2..6eb69d8391db4 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -25,8 +25,8 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_asset::AddAsset; -use bevy_ecs::{schedule::IntoSystemDescriptor, system::Resource}; -use bevy_render::{camera::CameraUpdateSystem, RenderApp, RenderSet}; +use bevy_ecs::prelude::*; +use bevy_render::{camera::CameraUpdateSystem, ExtractSchedule, RenderApp}; use bevy_sprite::SpriteSystem; use bevy_window::ModifiesWindows; use std::num::NonZeroUsize; @@ -91,11 +91,11 @@ impl Plugin for TextPlugin { .ambiguous_with(CameraUpdateSystem), ); - if let Some(render_app) = app.get_sub_app_mut(RenderLabel) { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app.edit_schedule(&ExtractSchedule, |extract_schedule| { extract_schedule .add_system(extract_text2d_sprite.after(SpriteSystem::ExtractSprites)); - }) + }); } } } From 29e166a8c247295e2601f301877da47a627a514f Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 23 Jan 2023 10:49:06 -0500 Subject: [PATCH 100/247] Get bevy_ui compiling --- crates/bevy_ui/src/lib.rs | 5 +---- crates/bevy_ui/src/stack.rs | 6 ++---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 283c16fb101fa..144d9194ba56f 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -31,10 +31,7 @@ pub mod prelude { } use bevy_app::prelude::*; -use bevy_ecs::{ - schedule::{IntoSystemDescriptor, SystemSet}, - system::Resource, -}; +use bevy_ecs::prelude::*; use bevy_input::InputSystem; use bevy_transform::TransformSystem; use bevy_window::ModifiesWindows; diff --git a/crates/bevy_ui/src/stack.rs b/crates/bevy_ui/src/stack.rs index 4df23559b8d6a..47cfac0d1021b 100644 --- a/crates/bevy_ui/src/stack.rs +++ b/crates/bevy_ui/src/stack.rs @@ -105,7 +105,7 @@ fn fill_stack_recursively(result: &mut Vec, stack: &mut StackingContext) mod tests { use bevy_ecs::{ component::Component, - schedule::{Schedule, Stage, SystemStage}, + schedule::Schedule, system::{CommandQueue, Commands}, world::World, }; @@ -183,9 +183,7 @@ mod tests { queue.apply(&mut world); let mut schedule = Schedule::default(); - let mut update_stage = SystemStage::parallel(); - update_stage.add_system(ui_stack_system); - schedule.add_stage("update", update_stage); + schedule.add_system(ui_stack_system); schedule.run(&mut world); let mut query = world.query::<&Label>(); From 4a471fdabd14e462e5ad7952ce5e3729b7efea9a Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 23 Jan 2023 10:50:39 -0500 Subject: [PATCH 101/247] Migrate bevy_pbr::prepare_lights and prepare_views --- crates/bevy_pbr/src/lib.rs | 10 +++------- crates/bevy_render/src/view/mod.rs | 13 ++++++++++++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index cdfde8764343b..702d5027c5f3c 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -51,7 +51,7 @@ use bevy_render::{ render_graph::RenderGraph, render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions}, render_resource::{Shader, SpecializedMeshPipelines}, - view::VisibilitySystems, + view::{ViewSet, VisibilitySystems}, ExtractSchedule, RenderApp, RenderSet, }; use bevy_transform::TransformSystem; @@ -245,18 +245,14 @@ impl Plugin for PbrPlugin { .add_system(render::extract_lights.in_set(RenderLightSystems::ExtractLights)); }) .add_system( - // this is added as an exclusive system because it contributes new views. it must run (and have Commands applied) - // _before_ the `prepare_views()` system is run. ideally this becomes a normal system when "stageless" features come out render::prepare_lights - .at_start() + .before(ViewSet::PrepareUniforms) .in_set(RenderLightSystems::PrepareLights) .in_set(RenderSet::Prepare), ) .add_system( - // NOTE: This needs to run after prepare_lights. As prepare_lights is an exclusive system, - // just adding it to the non-exclusive systems in the Prepare stage means it runs after - // prepare_lights. render::prepare_clusters + .after(render::prepare_lights) .in_set(RenderLightSystems::PrepareClusters) .in_set(RenderSet::Prepare), ) diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 87bb20f6612ed..459cf0efe2f7b 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -45,7 +45,11 @@ impl Plugin for ViewPlugin { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() - .add_system(prepare_view_uniforms.in_set(RenderSet::Prepare)) + .add_system( + prepare_view_uniforms + .in_set(RenderSet::Prepare) + .in_set(ViewSet::PrepareUniforms), + ) .add_system( prepare_view_targets .after(WindowSystem::Prepare) @@ -369,3 +373,10 @@ fn prepare_view_targets( } } } + +/// System sets for the [`view`](crate::view) module. +#[derive(SystemSet, PartialEq, Eq, Hash, Debug, Clone)] +pub enum ViewSet { + /// Prepares view uniforms + PrepareUniforms, +} From 4877dceba31eae852fa981dc57c130d4f5b0599f Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 23 Jan 2023 11:38:26 -0500 Subject: [PATCH 102/247] First pass of docs for references to stages --- crates/bevy_app/src/lib.rs | 6 ++-- crates/bevy_asset/src/lib.rs | 8 ++--- crates/bevy_ecs/README.md | 5 ++-- crates/bevy_ecs/src/system/commands/mod.rs | 10 ++++--- crates/bevy_ecs/src/system/mod.rs | 24 ++++++++------- crates/bevy_ecs/src/world/mod.rs | 2 +- crates/bevy_render/src/camera/camera.rs | 6 ++-- crates/bevy_render/src/extract_component.rs | 6 ++-- crates/bevy_render/src/lib.rs | 18 ++++++------ crates/bevy_render/src/pipelined_rendering.rs | 4 +-- crates/bevy_render/src/render_asset.rs | 6 ++-- crates/bevy_render/src/render_phase/mod.rs | 14 ++++----- .../src/render_resource/pipeline_cache.rs | 8 ++--- crates/bevy_render/src/view/visibility/mod.rs | 13 ++++----- crates/bevy_render/src/view/window.rs | 2 +- .../src/components/global_transform.rs | 2 +- .../src/components/transform.rs | 5 ++-- crates/bevy_transform/src/lib.rs | 4 +-- crates/bevy_ui/src/lib.rs | 2 +- examples/README.md | 13 +++++---- examples/ecs/ecs_guide.rs | 29 +++++-------------- examples/ecs/removal_detection.rs | 11 ++++--- 22 files changed, 97 insertions(+), 101 deletions(-) diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index a63c8dd14b169..54ce9e8d1725d 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -81,7 +81,7 @@ impl CoreSchedule { /// The corresponding [`SystemSets`](bevy_ecs::schedule::SystemSet) are added by [`App::add_default_sets`]. #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum CoreSet { - /// Runs before all other app stages. + /// Runs before all other members of this set. First, /// Runs systems that should only occur after a fixed period of time. /// @@ -95,11 +95,11 @@ pub enum CoreSet { Update, /// Runs after [`CoreSet::Update`]. PostUpdate, - /// Runs after all other app stages. + /// Runs after all other members of this set. Last, } -/// The names of the default [`App`] startup stages. +/// The names of the default [`App`] startup sets, which live in [`CoreSchedule::Startup`]. /// /// The corresponding [`SystemSets`](bevy_ecs::schedule::SystemSet) are added by [`App::add_default_sets`]. #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 1de5be7dab395..60118f797d8af 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -49,16 +49,16 @@ pub use reflect::*; use bevy_app::prelude::*; use bevy_ecs::prelude::*; -/// The names of asset stages in an [`App`] schedule. +/// [`SystemSet`]s for asset loading in an [`App`] schedule. #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum AssetSet { - /// The stage where asset storages are updated. + /// Asset storages are updated. LoadAssets, - /// The stage where asset events are generated. + /// Asset events are generated. AssetEvents, } -/// Adds support for Assets to an App. +/// Adds support for [`Assets`] to an App. /// /// Assets are typed collections with change tracking, which are added as App Resources. Examples of /// assets: textures, sounds, 3d models, maps, scenes diff --git a/crates/bevy_ecs/README.md b/crates/bevy_ecs/README.md index ced1a275876b2..3cfb7948bfa92 100644 --- a/crates/bevy_ecs/README.md +++ b/crates/bevy_ecs/README.md @@ -111,9 +111,10 @@ The [`resources.rs`](examples/resources.rs) example illustrates how to read and ### Schedules -Schedules consist of zero or more Stages, which run a set of Systems according to some execution strategy. Bevy ECS provides a few built in Stage implementations (ex: parallel, serial), but you can also implement your own! Schedules run Stages one-by-one in an order defined by the user. +Schedules run a set of Systems according to some execution strategy. +Systems can be added to any number of System Sets, which are used to control their scheduling metadata. -The built in "parallel stage" considers dependencies between systems and (by default) run as many of them in parallel as possible. This maximizes performance, while keeping the system execution safe. You can also define explicit dependencies between systems. +The built in "parallel executor" considers dependencies between systems and (by default) run as many of them in parallel as possible. This maximizes performance, while keeping the system execution safe. To control the system ordering, define explicit dependencies between systems and their sets. ## Using Bevy ECS diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index ed2d469b9b2f5..f1b393b568248 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -48,10 +48,11 @@ pub trait Command: Send + 'static { /// /// Since each command requires exclusive access to the `World`, /// all queued commands are automatically applied in sequence -/// only after each system in a [stage] has completed. +/// when the [`apply_system_buffers`] system runs. /// -/// The command queue of a system can also be manually applied +/// The command queue of an individual system can also be manually applied /// by calling [`System::apply_buffers`]. +/// Similarly, the command queue of a schedule can be manually applied via [`Schedule::apply_system_buffers`]. /// /// Each command can be used to modify the [`World`] in arbitrary ways: /// * spawning or despawning entities @@ -61,7 +62,7 @@ pub trait Command: Send + 'static { /// /// # Usage /// -/// Add `mut commands: Commands` as a function argument to your system to get a copy of this struct that will be applied at the end of the current stage. +/// Add `mut commands: Commands` as a function argument to your system to get a copy of this struct that will be applied the next time a copy of [`apply_system_buffers`] runs. /// Commands are almost always used as a [`SystemParam`](crate::system::SystemParam). /// /// ``` @@ -93,8 +94,9 @@ pub trait Command: Send + 'static { /// # } /// ``` /// -/// [stage]: crate::schedule::SystemStage /// [`System::apply_buffers`]: crate::system::System::apply_buffers +/// [`apply_system_buffers`]: crate::schedule::apply_system_buffers +/// [`Schedule::apply_system_buffers`]: crate::schedule::Schedule::apply_system_buffers pub struct Commands<'w, 's> { queue: &'s mut CommandQueue, entities: &'w Entities, diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 25bd22f2998a2..9b7b0ba8e4fb0 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1,8 +1,8 @@ //! Tools for controlling behavior in an ECS application. //! -//! Systems define how an ECS based application behaves. They have to be registered to a -//! [`SystemStage`](crate::schedule::SystemStage) to be able to run. A system is usually -//! written as a normal function that will be automatically converted into a system. +//! Systems define how an ECS based application behaves. +//! Systems are added to a [`Schedule`](crate::schedule::Schedule), which is then run. +//! A system is usually written as a normal function, which is automatically converted into a system. //! //! System functions can have parameters, through which one can query and mutate Bevy ECS state. //! Only types that implement [`SystemParam`] can be used, automatically fetching data from @@ -36,14 +36,18 @@ //! //! # System ordering //! -//! While the execution of systems is usually parallel and not deterministic, there are two -//! ways to determine a certain degree of execution order: +//! By default, the execution of systems is parallel and not deterministic. +//! Not all systems can run together: if a system mutably accesses data, +//! no other system that reads or writes that data can be run at the same time. +//! These systems are said to be **incompatible**. //! -//! - **System Stages:** They determine hard execution synchronization boundaries inside of -//! which systems run in parallel by default. -//! - **Labels:** Systems may be ordered within a stage using the methods `.before()` and `.after()`, -//! which order systems based on their [`SystemSet`]s. Each system is implicitly labeled with -//! its `fn` type, and custom labels may be added by calling `.in_set()`. +//! The relative order in which incompatible systems are run matters. +//! When this is not specified, a **system order ambiguity** exists in your schedule. +//! You can **explicitly order** systems: +//! +//! - by calling the `.before(this_system)` or `.after(that_system)` methods when adding them to your schedule +//! - by adding them to a [`SystemSet`], and then using `.configure_set(ThisSet.before(ThatSet))` syntax to configure many systems at once +//! - through the use of `.add_systems((system_a, system_b, system_c).chain()) //! //! [`SystemSet`]: crate::schedule::SystemSet //! diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index a92cc2f55dc16..d87ddd90160a9 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -622,7 +622,7 @@ impl World { /// of detection to be recorded. /// /// When using `bevy_ecs` as part of the full Bevy engine, this method is added as a system to the - /// main app, to run during the `CoreStage::Last`, so you don't need to call it manually. When using + /// main app, to run during `CoreSet::Last`, so you don't need to call it manually. When using /// `bevy_ecs` as a separate standalone crate however, you need to call this manually. /// /// ``` diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 53252ff453588..dac13b960a2f9 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -450,8 +450,8 @@ impl NormalizedRenderTarget { /// /// The system function is generic over the camera projection type, and only instances of /// [`OrthographicProjection`] and [`PerspectiveProjection`] are automatically added to -/// the app, as well as the runtime-selected [`Projection`]. The system runs during the -/// [`CoreStage::PostUpdate`] stage. +/// the app, as well as the runtime-selected [`Projection`]. +/// The system runs during [`CoreSet::PostUpdate`]. /// /// ## World Resources /// @@ -461,7 +461,7 @@ impl NormalizedRenderTarget { /// [`OrthographicProjection`]: crate::camera::OrthographicProjection /// [`PerspectiveProjection`]: crate::camera::PerspectiveProjection /// [`Projection`]: crate::camera::Projection -/// [`CoreStage::PostUpdate`]: bevy_app::CoreStage::PostUpdate +/// [`CoreSet::PostUpdate`]: bevy_app::CoreSet::PostUpdate pub fn camera_system( mut window_resized_events: EventReader, mut window_created_events: EventReader, diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index da2592479fe89..2c2c26395ff24 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -31,7 +31,7 @@ impl DynamicUniformIndex { /// Describes how a component gets extracted for rendering. /// /// Therefore the component is transferred from the "app world" into the "render world" -/// in the [`RenderStage::Extract`](crate::RenderStage::Extract) step. +/// in the [`ExtractSchedule`](crate::ExtractSchedule) step. pub trait ExtractComponent: Component { /// ECS [`WorldQuery`] to fetch the components to extract. type Query: WorldQuery + ReadOnlyWorldQuery; @@ -66,7 +66,7 @@ pub trait ExtractComponent: Component { /// For referencing the newly created uniforms a [`DynamicUniformIndex`] is inserted /// for every processed entity. /// -/// Therefore it sets up the [`RenderStage::Prepare`](crate::RenderStage::Prepare) step +/// Therefore it sets up the [`RenderSet::Prepare`](crate::RenderSet::Prepare) step /// for the specified [`ExtractComponent`]. pub struct UniformComponentPlugin(PhantomData C>); @@ -149,7 +149,7 @@ fn prepare_uniform_components( /// This plugin extracts the components into the "render world". /// -/// Therefore it sets up the [`RenderStage::Extract`](crate::RenderStage::Extract) step +/// Therefore it sets up the [`RenderSet::Extract`](crate::RenderSet::Extract) step /// for the specified [`ExtractComponent`]. pub struct ExtractComponentPlugin { only_extract_visible: bool, diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 03f7c8ac5b651..941bdf43c6cbc 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -68,15 +68,15 @@ pub struct RenderPlugin { /// The sets run in the order listed, with [`apply_system_buffers`] inserted between each set. #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum RenderSet { - /// A stage for applying the commands from the [`Extract`] stage + /// A system set for applying the commands from the [`ExtractSchedule`] ExtractCommands, /// Prepare render resources from the extracted data for the GPU. Prepare, /// Create [`BindGroups`](crate::render_resource::BindGroup) that depend on - /// [`Prepare`](RenderStage::Prepare) data and queue up draw calls to run during the - /// [`Render`](RenderStage::Render) stage. + /// [`Prepare`](RenderSet::Prepare) data and queue up draw calls to run during the + /// [`Render`](RenderSet::Render) step. Queue, // TODO: This could probably be moved in favor of a system ordering abstraction in Render or Queue @@ -149,8 +149,8 @@ struct RenderExtraction; pub struct ExtractSchedule; /// The simulation [`World`] of the application, stored as a resource. -/// This resource is only available during [`RenderStage::Extract`] and not -/// during command application of that stage. +/// This resource is only available during [`ExtractSchedule`] and not +/// during command application of that schedule. /// See [`Extract`] for more details. #[derive(Resource, Default)] pub struct MainWorld(World); @@ -180,7 +180,7 @@ pub mod main_graph { pub struct RenderApp; impl Plugin for RenderPlugin { - /// Initializes the renderer, sets up the [`RenderStage`](RenderStage) and creates the rendering sub-app. + /// Initializes the renderer, sets up the [`RenderSet`](RenderSet) and creates the rendering sub-app. fn build(&self, app: &mut App) { app.add_asset::() .add_debug_asset::() @@ -281,7 +281,7 @@ impl Plugin for RenderPlugin { assert_eq!( render_app.world.entities().len(), 0, - "An entity was spawned after the entity list was cleared last frame and before the extract stage began. This is not supported", + "An entity was spawned after the entity list was cleared last frame and before the extract schedule began. This is not supported", ); // This is safe given the clear_entities call in the past frame and the assert above @@ -319,11 +319,11 @@ impl Plugin for RenderPlugin { } /// A "scratch" world used to avoid allocating new worlds every frame when -/// swapping out the [`MainWorld`] for [`RenderStage::Extract`]. +/// swapping out the [`MainWorld`] for [`RenderSet::Extract`]. #[derive(Resource, Default)] struct ScratchMainWorld(World); -/// Executes the [`Extract`](RenderStage::Extract) stage of the renderer. +/// Executes the [`ExtractSchedule`] step of the renderer. /// This updates the render world with the extracted ECS data of the current frame. fn extract(main_world: &mut World, render_app: &mut App) { // temporarily add the app world to the render world as a resource diff --git a/crates/bevy_render/src/pipelined_rendering.rs b/crates/bevy_render/src/pipelined_rendering.rs index eecff73ab09f5..3a9fde3da2ec6 100644 --- a/crates/bevy_render/src/pipelined_rendering.rs +++ b/crates/bevy_render/src/pipelined_rendering.rs @@ -50,11 +50,11 @@ pub struct RenderToMainAppReceiver(pub Receiver); /// |--------------------------------------------------------------------| /// ``` /// -/// - `extract` is the stage where data is copied from the main world to the render world. +/// - `extract` is the step where data is copied from the main world to the render world. /// This is run on the main app's thread. /// - On the render thread, we first apply the `extract commands`. This is not run during extract, so the /// main schedule can start sooner. -/// - Then the `rendering schedule` is run. See [`crate::RenderStage`] for the available stages. +/// - Then the `rendering schedule` is run. See [`RenderSet`](crate::RenderSet) for the standard steps in this process. /// - In parallel to the rendering thread the [`RenderExtractApp`] schedule runs. By /// default this schedule is empty. But it is useful if you need something to run before I/O processing. /// - Next all the `winit events` are processed. diff --git a/crates/bevy_render/src/render_asset.rs b/crates/bevy_render/src/render_asset.rs index b041ce80fb5b3..dfaab37bab9a0 100644 --- a/crates/bevy_render/src/render_asset.rs +++ b/crates/bevy_render/src/render_asset.rs @@ -15,12 +15,12 @@ pub enum PrepareAssetError { /// Describes how an asset gets extracted and prepared for rendering. /// -/// In the [`RenderStage::Extract`](crate::RenderStage::Extract) step the asset is transferred -/// from the "app world" into the "render world". +/// In the [`ExtractSchedule`](crate::ExtractSchedule) step the asset is transferred +/// from the "main world" into the "render world". /// Therefore it is converted into a [`RenderAsset::ExtractedAsset`], which may be the same type /// as the render asset itself. /// -/// After that in the [`RenderStage::Prepare`](crate::RenderStage::Prepare) step the extracted asset +/// After that in the [`RenderSet::Prepare`](crate::RenderSet::Prepare) step the extracted asset /// is transformed into its GPU-representation of type [`RenderAsset::PreparedAsset`]. pub trait RenderAsset: Asset { /// The representation of the asset in the "render world". diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 54226a5d3b1d6..47d6a59c1b439 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -10,11 +10,11 @@ //! //! To draw an entity, a corresponding [`PhaseItem`] has to be added to one or multiple of these //! render phases for each view that it is visible in. -//! This must be done in the [`RenderStage::Queue`](crate::RenderStage::Queue). +//! This must be done in the [`RenderSet::Queue`](crate::RenderSet::Queue). //! After that the render phase sorts them in the -//! [`RenderStage::PhaseSort`](crate::RenderStage::PhaseSort). +//! [`RenderSet::PhaseSort`](crate::RenderSet::PhaseSort). //! Finally the items are rendered using a single [`TrackedRenderPass`], during the -//! [`RenderStage::Render`](crate::RenderStage::Render). +//! [`RenderSet::Render`](crate::RenderSet::Render). //! //! Therefore each phase item is assigned a [`Draw`] function. //! These set up the state of the [`TrackedRenderPass`] (i.e. select the @@ -122,13 +122,13 @@ impl RenderPhase { /// as part of a [`RenderPhase`]. /// /// The data required for rendering an entity is extracted from the main world in the -/// [`RenderStage::Extract`](crate::RenderStage::Extract). +/// [`ExtractSchedule`](crate::ExtractSchedule). /// Then it has to be queued up for rendering during the -/// [`RenderStage::Queue`](crate::RenderStage::Queue), by adding a corresponding phase item to +/// [`RenderSet::Queue`](crate::RenderSet::Queue), by adding a corresponding phase item to /// a render phase. /// Afterwards it will be sorted and rendered automatically in the -/// [`RenderStage::PhaseSort`](crate::RenderStage::PhaseSort) and -/// [`RenderStage::Render`](crate::RenderStage::Render), respectively. +/// [`RenderSet::PhaseSort`](crate::RenderSet::PhaseSort) and +/// [`RenderSet::Render`](crate::RenderSet::Render), respectively. pub trait PhaseItem: Sized + Send + Sync + 'static { /// The type used for ordering the items. The smallest values are drawn first. /// This order can be calculated using the [`ViewRangefinder3d`], diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index 36c54e6043e7d..debaa8bc82106 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -330,13 +330,13 @@ impl LayoutCache { /// The cache stores existing render and compute pipelines allocated on the GPU, as well as /// pending creation. Pipelines inserted into the cache are identified by a unique ID, which /// can be used to retrieve the actual GPU object once it's ready. The creation of the GPU -/// pipeline object is deferred to the [`RenderStage::Render`] stage, just before the render +/// pipeline object is deferred to the [`RenderSet::Render`] step, just before the render /// graph starts being processed, as this requires access to the GPU. /// /// Note that the cache do not perform automatic deduplication of identical pipelines. It is /// up to the user not to insert the same pipeline twice to avoid wasting GPU resources. /// -/// [`RenderStage::Render`]: crate::RenderStage::Render +/// [`RenderSet::Render`]: crate::RenderSet::Render #[derive(Resource)] pub struct PipelineCache { layout_cache: LayoutCache, @@ -630,10 +630,10 @@ impl PipelineCache { /// Process the pipeline queue and create all pending pipelines if possible. /// - /// This is generally called automatically during the [`RenderStage::Render`] stage, but can + /// This is generally called automatically during the [`RenderSet::Render`] step, but can /// be called manually to force creation at a different time. /// - /// [`RenderStage::Render`]: crate::RenderStage::Render + /// [`RenderSet::Render`]: crate::RenderSet::Render pub fn process_queue(&mut self) { let mut waiting_pipelines = mem::take(&mut self.waiting_pipelines); let mut pipelines = mem::take(&mut self.pipelines); diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index b8f1103e63a0d..3e7ffbe1b1ad5 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -91,8 +91,8 @@ impl ComputedVisibility { /// Whether this entity is visible to something this frame. This is true if and only if [`Self::is_visible_in_hierarchy`] and [`Self::is_visible_in_view`] /// are true. This is the canonical method to call to determine if an entity should be drawn. - /// This value is updated in [`CoreStage::PostUpdate`] during the [`VisibilitySystems::CheckVisibility`] system label. Reading it from the - /// [`CoreStage::Update`] stage will yield the value from the previous frame. + /// This value is updated in [`CoreSet::PostUpdate`] during the [`VisibilitySystems::CheckVisibility`] system label. + /// Reading it during [`CoreSet::Update`] will yield the value from the previous frame. #[inline] pub fn is_visible(&self) -> bool { self.flags.bits == ComputedVisibilityFlags::all().bits @@ -100,8 +100,7 @@ impl ComputedVisibility { /// Whether this entity is visible in the entity hierarchy, which is determined by the [`Visibility`] component. /// This takes into account "visibility inheritance". If any of this entity's ancestors (see [`Parent`]) are hidden, this entity - /// will be hidden as well. This value is updated in the [`CoreStage::PostUpdate`] stage in the - /// [`VisibilitySystems::VisibilityPropagate`] system label. + /// will be hidden as well. This value is updated in the [`VisibilitySystems::VisibilityPropagate`], which lives under the [`CoreSet::PostUpdate`] set. #[inline] pub fn is_visible_in_hierarchy(&self) -> bool { self.flags @@ -111,9 +110,9 @@ impl ComputedVisibility { /// Whether this entity is visible in _any_ view (Cameras, Lights, etc). Each entity type (and view type) should choose how to set this /// value. For cameras and drawn entities, this will take into account [`RenderLayers`]. /// - /// This value is reset to `false` every frame in [`VisibilitySystems::VisibilityPropagate`] during [`CoreStage::PostUpdate`]. - /// Each entity type then chooses how to set this field in the [`CoreStage::PostUpdate`] stage in the - /// [`VisibilitySystems::CheckVisibility`] system label. Meshes might use frustum culling to decide if they are visible in a view. + /// This value is reset to `false` every frame in [`VisibilitySystems::VisibilityPropagate`] during [`CoreSet::PostUpdate`]. + /// Each entity type then chooses how to set this field in the [`VisibilitySystems::CheckVisibility`] system set, under [`CoreSet::PostUpdate`]. + /// Meshes might use frustum culling to decide if they are visible in a view. /// Other entities might just set this to `true` every frame. #[inline] pub fn is_visible_in_view(&self) -> bool { diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index ae7fc6b383433..d2b5f54fe7f49 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -152,7 +152,7 @@ pub struct WindowSurfaces { /// Creates and (re)configures window surfaces, and obtains a swapchain texture for rendering. /// /// NOTE: `get_current_texture` in `prepare_windows` can take a long time if the GPU workload is -/// the performance bottleneck. This can be seen in profiles as multiple prepare-stage systems all +/// the performance bottleneck. This can be seen in profiles as multiple prepare-set systems all /// taking an unusually long time to complete, and all finishing at about the same time as the /// `prepare_windows` system. Improvements in bevy are planned to avoid this happening when it /// should not but it will still happen as it is easy for a user to create a large GPU workload diff --git a/crates/bevy_transform/src/components/global_transform.rs b/crates/bevy_transform/src/components/global_transform.rs index 71355f2693285..b349ea5a49a7f 100644 --- a/crates/bevy_transform/src/components/global_transform.rs +++ b/crates/bevy_transform/src/components/global_transform.rs @@ -24,7 +24,7 @@ use bevy_reflect::{std_traits::ReflectDefault, FromReflect, Reflect}; /// [`GlobalTransform`] is updated from [`Transform`] in the systems labeled /// [`TransformPropagate`](crate::TransformSystem::TransformPropagate). /// -/// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you +/// This system runs during [`CoreSet::PostUpdate`](crate::CoreSet::PostUpdate). If you /// update the [`Transform`] of an entity in this stage or after, you will notice a 1 frame lag /// before the [`GlobalTransform`] is updated. /// diff --git a/crates/bevy_transform/src/components/transform.rs b/crates/bevy_transform/src/components/transform.rs index 1a437788ebfb1..997955ac04b8e 100644 --- a/crates/bevy_transform/src/components/transform.rs +++ b/crates/bevy_transform/src/components/transform.rs @@ -23,8 +23,8 @@ use std::ops::Mul; /// [`GlobalTransform`] is updated from [`Transform`] in the systems labeled /// [`TransformPropagate`](crate::TransformSystem::TransformPropagate). /// -/// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you -/// update the [`Transform`] of an entity in this stage or after, you will notice a 1 frame lag +/// This system runs during [`CoreSet::PostUpdate`](crate::CoreSet::PostUpdate). If you +/// update the [`Transform`] of an entity during this set or after, you will notice a 1 frame lag /// before the [`GlobalTransform`] is updated. /// /// # Examples @@ -34,6 +34,7 @@ use std::ops::Mul; /// /// [`global_vs_local_translation`]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/global_vs_local_translation.rs /// [`transform`]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/transform.rs +/// [`Transform`]: super::Transform #[derive(Component, Debug, PartialEq, Clone, Copy, Reflect, FromReflect)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[reflect(Component, Default, PartialEq)] diff --git a/crates/bevy_transform/src/lib.rs b/crates/bevy_transform/src/lib.rs index dd354790219e6..eb252357ebaae 100644 --- a/crates/bevy_transform/src/lib.rs +++ b/crates/bevy_transform/src/lib.rs @@ -38,7 +38,7 @@ use prelude::{GlobalTransform, Transform}; /// [`GlobalTransform`] is updated from [`Transform`] in the systems labeled /// [`TransformPropagate`](crate::TransformSystem::TransformPropagate). /// -/// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you +/// This system runs during [`CoreSet::PostUpdate`](crate::CoreSet::PostUpdate). If you /// update the [`Transform`] of an entity in this stage or after, you will notice a 1 frame lag /// before the [`GlobalTransform`] is updated. #[derive(Bundle, Clone, Copy, Debug, Default)] @@ -59,7 +59,7 @@ impl TransformBundle { /// Creates a new [`TransformBundle`] from a [`Transform`]. /// /// This initializes [`GlobalTransform`] as identity, to be updated later by the - /// [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate) stage. + /// [`CoreSet::PostUpdate`](crate::CoreSet::PostUpdate) stage. #[inline] pub const fn from_transform(transform: Transform) -> Self { TransformBundle { diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 144d9194ba56f..fc8d64997899d 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -107,7 +107,7 @@ impl Plugin for UiPlugin { .in_set(CoreSet::PreUpdate) .after(InputSystem), ) - // add these stages to front because these must run before transform update systems + // add these systems to front because these must run before transform update systems .add_system( widget::text_system .in_set(CoreSet::PostUpdate) diff --git a/examples/README.md b/examples/README.md index f2733fcf5b88d..1cd6f2517003f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -56,19 +56,22 @@ git checkout v0.4.0 - [Transforms](#transforms) - [UI (User Interface)](#ui-user-interface) - [Window](#window) - - [Tests](#tests) - [Platform-Specific Examples](#platform-specific-examples) - [Android](#android) - [Setup](#setup) - - [Build & Run](#build--run) + - [Build \& Run](#build--run) + - [Debugging](#debugging) - [Old phones](#old-phones) - [iOS](#ios) - [Setup](#setup-1) - - [Build & Run](#build--run-1) + - [Build \& Run](#build--run-1) - [WASM](#wasm) - [Setup](#setup-2) - - [Build & Run](#build--run-2) + - [Build \& Run](#build--run-2) + - [Optimizing](#optimizing) + - [1. Tweak your `Cargo.toml`](#1-tweak-your-cargotoml) + - [2. Use `wasm-opt` from the binaryen package](#2-use-wasm-opt-from-the-binaryen-package) - [Loading Assets](#loading-assets) # The Bare Minimum @@ -201,7 +204,7 @@ Example | Description [Hierarchy](../examples/ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities [Iter Combinations](../examples/ecs/iter_combinations.rs) | Shows how to iterate over combinations of query results [Parallel Query](../examples/ecs/parallel_query.rs) | Illustrates parallel queries with `ParallelIterator` -[Removal Detection](../examples/ecs/removal_detection.rs) | Query for entities that had a specific component removed in a previous stage during the current frame +[Removal Detection](../examples/ecs/removal_detection.rs) | Query for entities that had a specific component removed earlier in the current frame [Startup System](../examples/ecs/startup_system.rs) | Demonstrates a startup system (one that runs once when the app starts up) [State](../examples/ecs/state.rs) | Illustrates how to use States to control transitioning from a Menu state to an InGame state [System Closure](../examples/ecs/system_closure.rs) | Show how to use closures as systems, and how to configure `Local` variables by capturing external state diff --git a/examples/ecs/ecs_guide.rs b/examples/ecs/ecs_guide.rs index 073c43df4ace6..30946f61d43e5 100644 --- a/examples/ecs/ecs_guide.rs +++ b/examples/ecs/ecs_guide.rs @@ -265,39 +265,26 @@ fn main() { .add_system(print_message_system) // SYSTEM EXECUTION ORDER // - // Each system belongs to a `Stage`, which controls the execution strategy and broad order - // of the systems within each tick. Startup stages (which startup systems are - // registered in) will always complete before ordinary stages begin, - // and every system in a stage must complete before the next stage advances. - // Once every stage has concluded, the main loop is complete and begins again. + // Each system belongs to a `Schedule`, which controls the execution strategy and broad order + // of the systems within each tick. The [`CoreSchedule::Startup`] schedule holds + // startup systems, which are run a single time before the [`CoreSchedule::Main`] runs. // // By default, all systems run in parallel, except when they require mutable access to a // piece of data. This is efficient, but sometimes order matters. // For example, we want our "game over" system to execute after all other systems to ensure // we don't accidentally run the game for an extra round. // - // Rather than splitting each of your systems into separate stages, you should force an - // explicit ordering between them by giving the relevant systems a label with - // `.label`, then using the `.before` or `.after` methods. Systems will not be - // scheduled until all of the systems that they have an "ordering dependency" on have + // You can force an explicit ordering between systems using the `.before` or `.after` methods. + // Systems will not be scheduled until all of the systems that they have an "ordering dependency" on have // completed. // - // Doing that will, in just about all cases, lead to better performance compared to - // splitting systems between stages, because it gives the scheduling algorithm more - // opportunities to run systems in parallel. - // Stages are still necessary, however: end of a stage is a hard sync point - // (meaning, no systems are running) where `Commands` issued by systems are processed. - // This is required because commands can perform operations that are incompatible with - // having systems in flight, such as spawning or deleting entities, - // adding or removing resources, etc. - // - // add_system(system) adds systems to the UPDATE stage by default + // add_system(system) adds systems to the Update system set by default // However we can manually specify the stage if we want to. The following is equivalent to // add_system(score_system) .add_system_to_stage(CoreSet::Update, score_system) - // There are other `CoreStages`, such as `Last` which runs at the very end of each run. + // There are other `CoreSets`, such as `Last` which runs at the very end of each run. .add_system_to_stage(CoreSet::Last, print_at_end_round) - // We can also create new stages. Here is what our games stage order will look like: + // We can also create new system sets. Here is what our games stage order will look like: // "before_round": new_player_system, new_round_system // "update": print_message_system, score_system // "after_round": score_check_system, game_over_system diff --git a/examples/ecs/removal_detection.rs b/examples/ecs/removal_detection.rs index 25b7805267441..4af31034a66b1 100644 --- a/examples/ecs/removal_detection.rs +++ b/examples/ecs/removal_detection.rs @@ -6,13 +6,12 @@ fn main() { // Information regarding removed `Component`s is discarded at the end of each frame, so you need // to react to the removal before the frame is over. // - // Also, `Components` are removed via a `Command`. `Command`s are applied after a stage has - // finished executing. So you need to react to the removal at some stage after the - // `Component` is removed. + // Also, `Components` are removed via a `Command`, which are not applied immediately. + // So you need to react to the removal at some stage after `apply_system_buffers` has run, + // and the Component` is removed. // - // With these constraints in mind we make sure to place the system that removes a `Component` on - // the `CoreStage::Update' stage, and the system that reacts on the removal on the - // `CoreStage::PostUpdate` stage. + // With these constraints in mind we make sure to place the system that removes a `Component` in + // `CoreSet::Update', and the system that reacts on the removal in `CoreSet::PostUpdate`. App::new() .add_plugins(DefaultPlugins) .add_startup_system(setup) From 2cea36cdcf81e8e19e0efdd09c5b54612bac11ac Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 23 Jan 2023 11:44:59 -0500 Subject: [PATCH 103/247] Remove usages of add_system_to_stage in tests and examples --- examples/ecs/ecs_guide.rs | 32 ++++++------------- examples/ecs/removal_detection.rs | 4 +-- .../shader/compute_shader_game_of_life.rs | 2 +- examples/shader/shader_instancing.rs | 4 +-- examples/tools/scene_viewer/main.rs | 2 +- .../tools/scene_viewer/scene_viewer_plugin.rs | 2 +- 6 files changed, 17 insertions(+), 29 deletions(-) diff --git a/examples/ecs/ecs_guide.rs b/examples/ecs/ecs_guide.rs index 30946f61d43e5..f6728ac6e735a 100644 --- a/examples/ecs/ecs_guide.rs +++ b/examples/ecs/ecs_guide.rs @@ -240,7 +240,7 @@ fn print_at_end_round(mut counter: Local) { } #[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] -enum MyStage { +enum MySet { BeforeRound, AfterRound, } @@ -281,35 +281,23 @@ fn main() { // add_system(system) adds systems to the Update system set by default // However we can manually specify the stage if we want to. The following is equivalent to // add_system(score_system) - .add_system_to_stage(CoreSet::Update, score_system) + .add_system(score_system.in_set(CoreSet::Update)) // There are other `CoreSets`, such as `Last` which runs at the very end of each run. - .add_system_to_stage(CoreSet::Last, print_at_end_round) + .add_system(print_at_end_round.in_set(CoreSet::Last)) // We can also create new system sets. Here is what our games stage order will look like: // "before_round": new_player_system, new_round_system // "update": print_message_system, score_system // "after_round": score_check_system, game_over_system - .add_stage_before( - CoreSet::Update, - MyStage::BeforeRound, - SystemStage::parallel(), - ) - .add_stage_after( - CoreSet::Update, - MyStage::AfterRound, - SystemStage::parallel(), - ) - .add_system_to_stage(MyStage::BeforeRound, new_round_system) - .add_system_to_stage( - MyStage::BeforeRound, - new_player_system.after(new_round_system), - ) - .add_system_to_stage(MyStage::BeforeRound, exclusive_player_system) - .add_system_to_stage(MyStage::AfterRound, score_check_system) - .add_system_to_stage( + .add_stage_before(CoreSet::Update, MySet::BeforeRound, SystemStage::parallel()) + .add_stage_after(CoreSet::Update, MySet::AfterRound, SystemStage::parallel()) + .add_system(new_round_system.in_set(MySet::BeforeRound)) + .add_system(new_player_system.after(new_round_system.in_set(MySet::BeforeRound))) + .add_system(exclusive_player_system.in_set(MySet::BeforeRound)) + .add_system(score_check_system.in_set(MySet::AfterRound)) + .add_system( // We can ensure that `game_over_system` runs after `score_check_system` using explicit ordering // To do this we use either `.before` or `.after` to describe the order we want the relationship // Since we are using `after`, `game_over_system` runs after `score_check_system` - MyStage::AfterRound, game_over_system.after(score_check_system), ) // We can check our systems for execution order ambiguities by examining the output produced diff --git a/examples/ecs/removal_detection.rs b/examples/ecs/removal_detection.rs index 4af31034a66b1..3892af12b5693 100644 --- a/examples/ecs/removal_detection.rs +++ b/examples/ecs/removal_detection.rs @@ -15,8 +15,8 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_startup_system(setup) - .add_system_to_stage(CoreSet::Update, remove_component) - .add_system_to_stage(CoreSet::PostUpdate, react_on_removal) + .add_system(remove_component.in_set(CoreSet::Update)) + .add_system(react_on_removal.in_set(CoreSet::PostUpdate)) .run(); } diff --git a/examples/shader/compute_shader_game_of_life.rs b/examples/shader/compute_shader_game_of_life.rs index e199322599d19..7e94820028604 100644 --- a/examples/shader/compute_shader_game_of_life.rs +++ b/examples/shader/compute_shader_game_of_life.rs @@ -74,7 +74,7 @@ impl Plugin for GameOfLifeComputePlugin { let render_app = app.sub_app_mut(RenderApp); render_app .init_resource::() - .add_system_to_stage(RenderSet::Queue, queue_bind_group); + .add_system(queue_bind_group.in_set(RenderSet::Queue)); let mut render_graph = render_app.world.resource_mut::(); render_graph.add_node("game_of_life", GameOfLifeNode::default()); diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index e0208b6c39c43..b6d0f29d1187f 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -85,8 +85,8 @@ impl Plugin for CustomMaterialPlugin { .add_render_command::() .init_resource::() .init_resource::>() - .add_system_to_stage(RenderSet::Queue, queue_custom) - .add_system_to_stage(RenderSet::Prepare, prepare_instance_buffers); + .add_system(queue_custom.in_set(RenderSet::Queue)) + .add_system(prepare_instance_buffers.in_set(RenderSet::Prepare)); } } diff --git a/examples/tools/scene_viewer/main.rs b/examples/tools/scene_viewer/main.rs index aca009746dc7c..5b37c881f8a6c 100644 --- a/examples/tools/scene_viewer/main.rs +++ b/examples/tools/scene_viewer/main.rs @@ -42,7 +42,7 @@ fn main() { .add_plugin(CameraControllerPlugin) .add_plugin(SceneViewerPlugin) .add_startup_system(setup) - .add_system_to_stage(CoreSet::PreUpdate, setup_scene_after_load); + .add_system(setup_scene_after_load.in_set(CoreSet::PreUpdate)); app.run(); } diff --git a/examples/tools/scene_viewer/scene_viewer_plugin.rs b/examples/tools/scene_viewer/scene_viewer_plugin.rs index a3a9dc279ad1b..edf62e8cc5062 100644 --- a/examples/tools/scene_viewer/scene_viewer_plugin.rs +++ b/examples/tools/scene_viewer/scene_viewer_plugin.rs @@ -60,7 +60,7 @@ pub struct SceneViewerPlugin; impl Plugin for SceneViewerPlugin { fn build(&self, app: &mut App) { app.init_resource::() - .add_system_to_stage(CoreSet::PreUpdate, scene_load_check) + .add_system(scene_load_check.in_set(CoreSet::PreUpdate)) .add_system(update_lights) .add_system(camera_tracker); From cd69bbb274168c2ade02389f80767cde2196943a Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 23 Jan 2023 12:04:30 -0500 Subject: [PATCH 104/247] Remove simple usages of SystemStage --- .../bevy_ecs/components/archetype_updates.rs | 28 ++++---- benches/benches/bevy_ecs/empty_archetypes.rs | 39 +++++------ .../bevy_ecs/scheduling/run_criteria.rs | 66 +++++++++---------- .../benches/bevy_ecs/scheduling/schedule.rs | 14 ++-- crates/bevy_app/src/app.rs | 12 ++-- crates/bevy_ecs/README.md | 7 +- crates/bevy_ecs/src/system/commands/mod.rs | 8 +-- crates/bevy_ecs/src/system/mod.rs | 2 +- crates/bevy_ecs/src/system/system_param.rs | 3 +- crates/bevy_ecs/src/system/system_piping.rs | 9 --- crates/bevy_time/src/time.rs | 6 +- examples/ecs/ecs_guide.rs | 4 +- 12 files changed, 86 insertions(+), 112 deletions(-) diff --git a/benches/benches/bevy_ecs/components/archetype_updates.rs b/benches/benches/bevy_ecs/components/archetype_updates.rs index 8cb4c5552db0c..9c9d3d3d0d6d4 100644 --- a/benches/benches/bevy_ecs/components/archetype_updates.rs +++ b/benches/benches/bevy_ecs/components/archetype_updates.rs @@ -1,22 +1,18 @@ -use bevy_ecs::{ - component::Component, - schedule::{Stage, SystemStage}, - world::World, -}; +use bevy_ecs::{component::Component, world::World}; use criterion::{BenchmarkId, Criterion}; #[derive(Component)] struct A(f32); -fn setup(system_count: usize) -> (World, SystemStage) { +fn setup(system_count: usize) -> (World, Schedule) { let mut world = World::new(); fn empty() {} - let mut stage = SystemStage::parallel(); + let mut schedule = Schedule::new(); for _ in 0..system_count { - stage.add_system(empty); + schedule.add_system(empty); } - stage.run(&mut world); - (world, stage) + schedule.run(&mut world); + (world, schedule) } /// create `count` entities with distinct archetypes @@ -78,13 +74,13 @@ pub fn no_archetypes(criterion: &mut Criterion) { let mut group = criterion.benchmark_group("no_archetypes"); for i in 0..=5 { let system_count = i * 20; - let (mut world, mut stage) = setup(system_count); + let (mut world, mut schedule) = setup(system_count); group.bench_with_input( BenchmarkId::new("system_count", system_count), &system_count, |bencher, &_system_count| { bencher.iter(|| { - stage.run(&mut world); + schedule.run(&mut world); }); }, ); @@ -101,12 +97,12 @@ pub fn added_archetypes(criterion: &mut Criterion) { |bencher, &archetype_count| { bencher.iter_batched( || { - let (mut world, stage) = setup(SYSTEM_COUNT); + let (mut world, schedule) = setup(SYSTEM_COUNT); add_archetypes(&mut world, archetype_count); - (world, stage) + (world, schedule) }, - |(mut world, mut stage)| { - stage.run(&mut world); + |(mut world, mut schedule)| { + schedule.run(&mut world); }, criterion::BatchSize::LargeInput, ); diff --git a/benches/benches/bevy_ecs/empty_archetypes.rs b/benches/benches/bevy_ecs/empty_archetypes.rs index 0db82700e691e..0d01bdcc32830 100644 --- a/benches/benches/bevy_ecs/empty_archetypes.rs +++ b/benches/benches/bevy_ecs/empty_archetypes.rs @@ -1,9 +1,4 @@ -use bevy_ecs::{ - component::Component, - prelude::*, - schedule::{Stage, SystemStage}, - world::World, -}; +use bevy_ecs::{component::Component, prelude::*, world::World}; use bevy_tasks::{ComputeTaskPool, TaskPool}; use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; @@ -80,14 +75,14 @@ fn par_for_each( }); } -fn setup(parallel: bool, setup: impl FnOnce(&mut SystemStage)) -> (World, SystemStage) { +fn setup(parallel: bool, setup: impl FnOnce(&mut Schedule)) -> (World, Schedule) { let mut world = World::new(); - let mut stage = SystemStage::parallel(); + let mut schedule = Schedule::new(); if parallel { world.insert_resource(ComputeTaskPool(TaskPool::default())); } - setup(&mut stage); - (world, stage) + setup(&mut schedule); + (world, schedule) } /// create `count` entities with distinct archetypes @@ -158,8 +153,8 @@ fn add_archetypes(world: &mut World, count: u16) { fn empty_archetypes(criterion: &mut Criterion) { let mut group = criterion.benchmark_group("empty_archetypes"); for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] { - let (mut world, mut stage) = setup(true, |stage| { - stage.add_system(iter); + let (mut world, mut schedule) = setup(true, |schedule| { + schedule.add_system(iter); }); add_archetypes(&mut world, archetype_count); world.clear_entities(); @@ -177,20 +172,20 @@ fn empty_archetypes(criterion: &mut Criterion) { e.insert(A::<10>(1.0)); e.insert(A::<11>(1.0)); e.insert(A::<12>(1.0)); - stage.run(&mut world); + schedule.run(&mut world); group.bench_with_input( BenchmarkId::new("iter", archetype_count), &archetype_count, |bencher, &_| { bencher.iter(|| { - stage.run(&mut world); + schedule.run(&mut world); }) }, ); } for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] { - let (mut world, mut stage) = setup(true, |stage| { - stage.add_system(for_each); + let (mut world, mut schedule) = setup(true, |schedule| { + schedule.add_system(for_each); }); add_archetypes(&mut world, archetype_count); world.clear_entities(); @@ -208,20 +203,20 @@ fn empty_archetypes(criterion: &mut Criterion) { e.insert(A::<10>(1.0)); e.insert(A::<11>(1.0)); e.insert(A::<12>(1.0)); - stage.run(&mut world); + schedule.run(&mut world); group.bench_with_input( BenchmarkId::new("for_each", archetype_count), &archetype_count, |bencher, &_| { bencher.iter(|| { - stage.run(&mut world); + schedule.run(&mut world); }) }, ); } for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] { - let (mut world, mut stage) = setup(true, |stage| { - stage.add_system(par_for_each); + let (mut world, mut schedule) = setup(true, |schedule| { + schedule.add_system(par_for_each); }); add_archetypes(&mut world, archetype_count); world.clear_entities(); @@ -239,13 +234,13 @@ fn empty_archetypes(criterion: &mut Criterion) { e.insert(A::<10>(1.0)); e.insert(A::<11>(1.0)); e.insert(A::<12>(1.0)); - stage.run(&mut world); + schedule.run(&mut world); group.bench_with_input( BenchmarkId::new("par_for_each", archetype_count), &archetype_count, |bencher, &_| { bencher.iter(|| { - stage.run(&mut world); + schedule.run(&mut world); }) }, ); diff --git a/benches/benches/bevy_ecs/scheduling/run_criteria.rs b/benches/benches/bevy_ecs/scheduling/run_criteria.rs index 7ba91f704c28a..89e1c1be1d47f 100644 --- a/benches/benches/bevy_ecs/scheduling/run_criteria.rs +++ b/benches/benches/bevy_ecs/scheduling/run_criteria.rs @@ -1,10 +1,6 @@ -use bevy_ecs::{prelude::*, schedule::ShouldRun}; +use bevy_ecs::prelude::*; use criterion::Criterion; -fn run_stage(stage: &mut SystemStage, world: &mut World) { - stage.run(world); -} - /// Labels for run criteria which either always return yes, or always return no. #[derive(RunCriteriaLabel)] enum Always { @@ -22,11 +18,11 @@ pub fn run_criteria_yes(criterion: &mut Criterion) { ShouldRun::Yes } for amount in 0..21 { - let mut stage = SystemStage::parallel(); - stage.add_system(empty.with_run_criteria(always_yes)); + let mut schedule = Schedule::parallel(); + schedule.add_system(empty.with_run_criteria(always_yes)); for _ in 0..amount { // TODO: should change this to use a label or have another bench that uses a label instead - stage + schedule .add_system(empty.with_run_criteria(always_yes)) .add_system(empty.with_run_criteria(always_yes)) .add_system(empty.with_run_criteria(always_yes)) @@ -34,10 +30,10 @@ pub fn run_criteria_yes(criterion: &mut Criterion) { .add_system(empty.with_run_criteria(always_yes)); } // run once to initialize systems - run_stage(&mut stage, &mut world); + run_schedule(&mut schedule, &mut world); group.bench_function(&format!("{:03}_systems", 5 * amount + 1), |bencher| { bencher.iter(|| { - run_stage(&mut stage, &mut world); + run_schedule(&mut schedule, &mut world); }); }); } @@ -54,10 +50,10 @@ pub fn run_criteria_no(criterion: &mut Criterion) { ShouldRun::No } for amount in 0..21 { - let mut stage = SystemStage::parallel(); - stage.add_system(empty.with_run_criteria(always_no)); + let mut schedule = Schedule::parallel(); + schedule.add_system(empty.with_run_criteria(always_no)); for _ in 0..amount { - stage + schedule .add_system(empty.with_run_criteria(always_no)) .add_system(empty.with_run_criteria(always_no)) .add_system(empty.with_run_criteria(always_no)) @@ -65,10 +61,10 @@ pub fn run_criteria_no(criterion: &mut Criterion) { .add_system(empty.with_run_criteria(always_no)); } // run once to initialize systems - run_stage(&mut stage, &mut world); + run_schedule(&mut schedule, &mut world); group.bench_function(&format!("{:03}_systems", 5 * amount + 1), |bencher| { bencher.iter(|| { - run_stage(&mut stage, &mut world); + run_schedule(&mut schedule, &mut world); }); }); } @@ -85,11 +81,11 @@ pub fn run_criteria_yes_with_labels(criterion: &mut Criterion) { ShouldRun::Yes } for amount in 0..21 { - let mut stage = SystemStage::parallel(); + let mut schedule = Schedule::parallel(); - stage.add_system(empty.with_run_criteria(always_yes.in_set(Always::Yes))); + schedule.add_system(empty.with_run_criteria(always_yes.in_set(Always::Yes))); for _ in 0..amount { - stage + schedule .add_system(empty.with_run_criteria(Always::Yes)) .add_system(empty.with_run_criteria(Always::Yes)) .add_system(empty.with_run_criteria(Always::Yes)) @@ -97,10 +93,10 @@ pub fn run_criteria_yes_with_labels(criterion: &mut Criterion) { .add_system(empty.with_run_criteria(Always::Yes)); } // run once to initialize systems - run_stage(&mut stage, &mut world); + run_schedule(&mut schedule, &mut world); group.bench_function(&format!("{:03}_systems", 5 * amount + 1), |bencher| { bencher.iter(|| { - run_stage(&mut stage, &mut world); + run_schedule(&mut schedule, &mut world); }); }); } @@ -117,11 +113,11 @@ pub fn run_criteria_no_with_labels(criterion: &mut Criterion) { ShouldRun::No } for amount in 0..21 { - let mut stage = SystemStage::parallel(); + let mut schedule = Schedule::parallel(); - stage.add_system(empty.with_run_criteria(always_no.in_set(Always::No))); + schedule.add_system(empty.with_run_criteria(always_no.in_set(Always::No))); for _ in 0..amount { - stage + schedule .add_system(empty.with_run_criteria(Always::No)) .add_system(empty.with_run_criteria(Always::No)) .add_system(empty.with_run_criteria(Always::No)) @@ -129,10 +125,10 @@ pub fn run_criteria_no_with_labels(criterion: &mut Criterion) { .add_system(empty.with_run_criteria(Always::No)); } // run once to initialize systems - run_stage(&mut stage, &mut world); + run_schedule(&mut schedule, &mut world); group.bench_function(&format!("{:03}_systems", 5 * amount + 1), |bencher| { bencher.iter(|| { - run_stage(&mut stage, &mut world); + run_schedule(&mut schedule, &mut world); }); }); } @@ -153,10 +149,10 @@ pub fn run_criteria_yes_with_query(criterion: &mut Criterion) { query.single().0.into() } for amount in 0..21 { - let mut stage = SystemStage::parallel(); - stage.add_system(empty.with_run_criteria(yes_with_query)); + let mut schedule = Schedule::parallel(); + schedule.add_system(empty.with_run_criteria(yes_with_query)); for _ in 0..amount { - stage + schedule .add_system(empty.with_run_criteria(yes_with_query)) .add_system(empty.with_run_criteria(yes_with_query)) .add_system(empty.with_run_criteria(yes_with_query)) @@ -164,10 +160,10 @@ pub fn run_criteria_yes_with_query(criterion: &mut Criterion) { .add_system(empty.with_run_criteria(yes_with_query)); } // run once to initialize systems - run_stage(&mut stage, &mut world); + run_schedule(&mut schedule, &mut world); group.bench_function(&format!("{:03}_systems", 5 * amount + 1), |bencher| { bencher.iter(|| { - run_stage(&mut stage, &mut world); + run_schedule(&mut schedule, &mut world); }); }); } @@ -185,10 +181,10 @@ pub fn run_criteria_yes_with_resource(criterion: &mut Criterion) { res.0.into() } for amount in 0..21 { - let mut stage = SystemStage::parallel(); - stage.add_system(empty.with_run_criteria(yes_with_resource)); + let mut schedule = Schedule::parallel(); + schedule.add_system(empty.with_run_criteria(yes_with_resource)); for _ in 0..amount { - stage + schedule .add_system(empty.with_run_criteria(yes_with_resource)) .add_system(empty.with_run_criteria(yes_with_resource)) .add_system(empty.with_run_criteria(yes_with_resource)) @@ -196,10 +192,10 @@ pub fn run_criteria_yes_with_resource(criterion: &mut Criterion) { .add_system(empty.with_run_criteria(yes_with_resource)); } // run once to initialize systems - run_stage(&mut stage, &mut world); + run_schedule(&mut schedule, &mut world); group.bench_function(&format!("{:03}_systems", 5 * amount + 1), |bencher| { bencher.iter(|| { - run_stage(&mut stage, &mut world); + run_schedule(&mut schedule, &mut world); }); }); } diff --git a/benches/benches/bevy_ecs/scheduling/schedule.rs b/benches/benches/bevy_ecs/scheduling/schedule.rs index c6a41e25ce420..b7ed8d9429f10 100644 --- a/benches/benches/bevy_ecs/scheduling/schedule.rs +++ b/benches/benches/bevy_ecs/scheduling/schedule.rs @@ -46,13 +46,13 @@ pub fn schedule(c: &mut Criterion) { world.spawn_batch((0..10000).map(|_| (A(0.0), B(0.0), C(0.0), E(0.0)))); - let mut stage = SystemStage::parallel(); - stage.add_system(ab); - stage.add_system(cd); - stage.add_system(ce); - stage.run(&mut world); + let mut schedule = Schedule::parallel(); + schedule.add_system(ab); + schedule.add_system(cd); + schedule.add_system(ce); + schedule.run(&mut world); - b.iter(move || stage.run(&mut world)); + b.iter(move || schedule.run(&mut world)); }); group.finish(); } @@ -80,7 +80,7 @@ pub fn build_schedule(criterion: &mut Criterion) { group.measurement_time(std::time::Duration::from_secs(15)); // Method: generate a set of `graph_size` systems which have a One True Ordering. - // Add system to the stage with full constraints. Hopefully this should be maximimally + // Add system to the schedule with full constraints. Hopefully this should be maximimally // difficult for bevy to figure out. // Also, we are performing the `as_label` operation outside of the loop since that // requires an allocation and a leak. This is not something that would be necessary in a diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 7ab7741e4cbb0..cbddca3d13aea 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -103,23 +103,23 @@ impl Debug for App { /// #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] /// struct ExampleApp; /// -/// #[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] -/// struct ExampleStage; +/// #[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)] +/// struct ExampleSchedule; /// /// let mut app = App::empty(); /// // initialize the main app with a value of 0; /// app.insert_resource(Val(10)); /// -/// // create a app with a resource and a single stage +/// // create a app with a resource and a single schedule /// let mut sub_app = App::empty(); /// sub_app.insert_resource(Val(100)); -/// let mut example_stage = SystemStage::single_threaded(); -/// example_stage.add_system(|counter: Res| { +/// let mut example_schedule = Schedule::single_threaded(); +/// example_schedule.add_system(|counter: Res| { /// // since we assigned the value from the main world in extract /// // we see that value instead of 100 /// assert_eq!(counter.0, 10); /// }); -/// sub_app.add_stage(ExampleStage, example_stage); +/// sub_app.add_schedule(ExampleSchedule, example_schedule); /// /// // add the sub_app to the app /// app.add_sub_app(ExampleApp, sub_app, |main_world, sub_app| { diff --git a/crates/bevy_ecs/README.md b/crates/bevy_ecs/README.md index 3cfb7948bfa92..ee9126e1b5fb7 100644 --- a/crates/bevy_ecs/README.md +++ b/crates/bevy_ecs/README.md @@ -149,11 +149,8 @@ fn main() { // Create a new Schedule, which defines an execution strategy for Systems let mut schedule = Schedule::default(); - // Add a Stage to our schedule. Each Stage in a schedule runs all of its systems - // before moving on to the next Stage - schedule.add_stage(UpdateLabel, SystemStage::parallel() - .with_system(movement) - ); + // Add our system to the schedule + schedule.add_system(movement); // Run the schedule once. If your app has a "loop", you would run this once per loop schedule.run(&mut world); diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index f1b393b568248..0f903796081df 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -567,11 +567,11 @@ impl<'w, 's> Commands<'w, 's> { /// # let mut world = World::new(); /// # world.init_resource::(); /// # -/// # let mut setup_stage = SystemStage::single_threaded().with_system(setup); -/// # let mut assert_stage = SystemStage::single_threaded().with_system(assert_names); +/// # let mut setup_schedule = Schedule::new().add_system(setup); +/// # let mut assert_schedule = Schedule::new().add_system(assert_names); /// # -/// # setup_stage.run(&mut world); -/// # assert_stage.run(&mut world); +/// # setup_schedule.run(&mut world); +/// # assert_schedule.run(&mut world); /// /// fn setup(mut commands: Commands) { /// commands.spawn_empty().add(CountName); diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 9b7b0ba8e4fb0..0e35e01110216 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -55,7 +55,7 @@ //! //! ``` //! # use bevy_ecs::prelude::*; -//! # let mut app = SystemStage::single_threaded(); +//! # let mut app = Schedule::new(); //! // Prints "Hello, World!" each frame. //! app //! .add_system(print_first.before(print_mid)) diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index b6dcc66ebb1d0..0c0967530769c 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -385,8 +385,7 @@ impl_param_set!(); /// /// ``` /// # let mut world = World::default(); -/// # let mut schedule = Schedule::default(); -/// # schedule.add_stage("update", SystemStage::parallel()); +/// # let mut schedule = Schedule::new(); /// # use bevy_ecs::prelude::*; /// #[derive(Resource)] /// struct MyResource { value: u32 } diff --git a/crates/bevy_ecs/src/system/system_piping.rs b/crates/bevy_ecs/src/system/system_piping.rs index c05eac8bdd5cf..b22b9ad917dd3 100644 --- a/crates/bevy_ecs/src/system/system_piping.rs +++ b/crates/bevy_ecs/src/system/system_piping.rs @@ -227,7 +227,6 @@ pub mod adapter { /// # enum CoreStage { Update }; /// /// // Building a new schedule/app... - /// # use bevy_ecs::schedule::SystemStage; /// # let mut sched = Schedule::default(); sched /// .add_system( /// // Panic if the load system returns an error. @@ -262,7 +261,6 @@ pub mod adapter { /// # enum CoreStage { Update }; /// /// // Building a new schedule/app... - /// # use bevy_ecs::schedule::SystemStage; /// # let mut sched = Schedule::default(); sched /// .add_system( /// // Prints system information. @@ -293,7 +291,6 @@ pub mod adapter { /// # enum CoreStage { Update }; /// /// // Building a new schedule/app... - /// # use bevy_ecs::schedule::SystemStage; /// # let mut sched = Schedule::default(); sched /// .add_system( /// CoreStage::Update, @@ -325,7 +322,6 @@ pub mod adapter { /// # enum CoreStage { Update }; /// /// // Building a new schedule/app... - /// # use bevy_ecs::schedule::SystemStage; /// # let mut sched = Schedule::default(); sched /// .add_system( /// // Prints system warning if system returns an error. @@ -353,12 +349,7 @@ pub mod adapter { /// /// ``` /// use bevy_ecs::prelude::*; - /// # - /// # #[derive(StageLabel)] - /// # enum CoreStage { Update }; - /// /// // Building a new schedule/app... - /// # use bevy_ecs::schedule::SystemStage; /// # let mut sched = Schedule::default(); sched /// .add_system( /// CoreStage::Update, diff --git a/crates/bevy_time/src/time.rs b/crates/bevy_time/src/time.rs index 111ba6f74574d..b79ff5a9550a6 100644 --- a/crates/bevy_time/src/time.rs +++ b/crates/bevy_time/src/time.rs @@ -120,8 +120,8 @@ impl Time { /// world.insert_resource(time); /// world.insert_resource(Health { health_value: 0.2 }); /// - /// let mut update_stage = SystemStage::parallel(); - /// update_stage.add_system(health_system); + /// let mut schedule = Schedule::new(); + /// schedule.add_system(health_system); /// /// // Simulate that 30 ms have passed /// let mut time = world.resource_mut::

(self, condition: impl Condition

) -> SystemSetConfig; + /// Add this set to the [`OnEnter(state)`](OnEnter) set. + fn on_enter(self, state: impl States) -> SystemSetConfig; + /// Add this set to the [`OnUpdate(state)`](OnUpdate) set. + fn on_update(self, state: impl States) -> SystemSetConfig; + /// Add this set to the [`OnExit(state)`](OnExit) set. + fn on_exit(self, state: impl States) -> SystemSetConfig; /// Suppress warnings and errors that would result from systems in this set having ambiguities /// (conflicting access but indeterminate order) with systems in `set`. fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemSetConfig; @@ -119,27 +126,39 @@ where } fn in_set(self, set: impl SystemSet) -> SystemSetConfig { - SystemSetConfig::new(Box::new(self)).in_set(set) + self.into_config().in_set(set) } fn before(self, set: impl IntoSystemSet) -> SystemSetConfig { - SystemSetConfig::new(Box::new(self)).before(set) + self.into_config().before(set) } fn after(self, set: impl IntoSystemSet) -> SystemSetConfig { - SystemSetConfig::new(Box::new(self)).after(set) + self.into_config().after(set) } fn run_if

(self, condition: impl Condition

) -> SystemSetConfig { - SystemSetConfig::new(Box::new(self)).run_if(condition) + self.into_config().run_if(condition) + } + + fn on_enter(self, state: impl States) -> SystemSetConfig { + self.into_config().on_enter(state) + } + + fn on_update(self, state: impl States) -> SystemSetConfig { + self.into_config().on_update(state) + } + + fn on_exit(self, state: impl States) -> SystemSetConfig { + self.into_config().on_exit(state) } fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemSetConfig { - SystemSetConfig::new(Box::new(self)).ambiguous_with(set) + self.into_config().ambiguous_with(set) } fn ambiguous_with_all(self) -> SystemSetConfig { - SystemSetConfig::new(Box::new(self)).ambiguous_with_all() + self.into_config().ambiguous_with_all() } } @@ -149,27 +168,39 @@ impl IntoSystemSetConfig for BoxedSystemSet { } fn in_set(self, set: impl SystemSet) -> SystemSetConfig { - SystemSetConfig::new(self).in_set(set) + self.into_config().in_set(set) } fn before(self, set: impl IntoSystemSet) -> SystemSetConfig { - SystemSetConfig::new(self).before(set) + self.into_config().before(set) } fn after(self, set: impl IntoSystemSet) -> SystemSetConfig { - SystemSetConfig::new(self).after(set) + self.into_config().after(set) } fn run_if

(self, condition: impl Condition

) -> SystemSetConfig { - SystemSetConfig::new(self).run_if(condition) + self.into_config().run_if(condition) + } + + fn on_enter(self, state: impl States) -> SystemSetConfig { + self.into_config().on_enter(state) + } + + fn on_update(self, state: impl States) -> SystemSetConfig { + self.into_config().on_update(state) + } + + fn on_exit(self, state: impl States) -> SystemSetConfig { + self.into_config().on_exit(state) } fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemSetConfig { - SystemSetConfig::new(self).ambiguous_with(set) + self.into_config().ambiguous_with(set) } fn ambiguous_with_all(self) -> SystemSetConfig { - SystemSetConfig::new(self).ambiguous_with_all() + self.into_config().ambiguous_with_all() } } @@ -208,6 +239,21 @@ impl IntoSystemSetConfig for SystemSetConfig { self } + fn on_enter(self, state: impl States) -> SystemSetConfig { + self.in_set(OnEnter(state)); + self + } + + fn on_update(self, state: impl States) -> SystemSetConfig { + self.in_set(OnUpdate(state)); + self + } + + fn on_exit(self, state: impl States) -> SystemSetConfig { + self.in_set(OnExit(state)); + self + } + fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { ambiguous_with(&mut self.graph_info, Box::new(set.into_system_set())); self @@ -238,6 +284,12 @@ pub trait IntoSystemConfig: sealed::IntoSystemConfig { /// The `Condition` will be evaluated at most once (per schedule run), /// when the system prepares to run. fn run_if

(self, condition: impl Condition

) -> SystemConfig; + /// Add this system to the [`OnEnter(state)`](OnEnter) set. + fn on_enter(self, state: impl States) -> SystemSetConfig; + /// Add this system to the [`OnUpdate(state)`](OnUpdate) set. + fn on_update(self, state: impl States) -> SystemSetConfig; + /// Add this system to the [`OnExit(state)`](OnExit) set. + fn on_exit(self, state: impl States) -> SystemSetConfig; /// Suppress warnings and errors that would result from this system having ambiguities /// (conflicting access but indeterminate order) with systems in `set`. fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemConfig; @@ -255,27 +307,39 @@ where } fn in_set(self, set: impl SystemSet) -> SystemConfig { - SystemConfig::new(Box::new(IntoSystem::into_system(self))).in_set(set) + self.into_config().in_set(set) } fn before(self, set: impl IntoSystemSet) -> SystemConfig { - SystemConfig::new(Box::new(IntoSystem::into_system(self))).before(set) + self.into_config().before(set) } fn after(self, set: impl IntoSystemSet) -> SystemConfig { - SystemConfig::new(Box::new(IntoSystem::into_system(self))).after(set) + self.into_config().after(set) } fn run_if

(self, condition: impl Condition

) -> SystemConfig { - SystemConfig::new(Box::new(IntoSystem::into_system(self))).run_if(condition) + self.into_config().run_if(condition) + } + + fn on_enter(self, state: impl States) -> SystemSetConfig { + self.into_config().on_enter(state) + } + + fn on_update(self, state: impl States) -> SystemSetConfig { + self.into_config().on_update(state) + } + + fn on_exit(self, state: impl States) -> SystemSetConfig { + self.into_config().on_exit(state) } fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemConfig { - SystemConfig::new(Box::new(IntoSystem::into_system(self))).ambiguous_with(set) + self.into_config().ambiguous_with(set) } fn ambiguous_with_all(self) -> SystemConfig { - SystemConfig::new(Box::new(IntoSystem::into_system(self))).ambiguous_with_all() + self.into_config().ambiguous_with_all() } } @@ -285,27 +349,39 @@ impl IntoSystemConfig<()> for BoxedSystem<(), ()> { } fn in_set(self, set: impl SystemSet) -> SystemConfig { - SystemConfig::new(self).in_set(set) + self.into_config().in_set(set) } fn before(self, set: impl IntoSystemSet) -> SystemConfig { - SystemConfig::new(self).before(set) + self.into_config().before(set) } fn after(self, set: impl IntoSystemSet) -> SystemConfig { - SystemConfig::new(self).after(set) + self.into_config().after(set) } fn run_if

(self, condition: impl Condition

) -> SystemConfig { - SystemConfig::new(self).run_if(condition) + self.into_config().run_if(condition) + } + + fn on_enter(self, state: impl States) -> SystemSetConfig { + self.into_config().on_enter(state) + } + + fn on_update(self, state: impl States) -> SystemSetConfig { + self.into_config().on_update(state) + } + + fn on_exit(self, state: impl States) -> SystemSetConfig { + self.into_config().on_exit(state) } fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemConfig { - SystemConfig::new(self).ambiguous_with(set) + self.into_config().ambiguous_with(set) } fn ambiguous_with_all(self) -> SystemConfig { - SystemConfig::new(self).ambiguous_with_all() + self.into_config().ambiguous_with_all() } } @@ -344,6 +420,21 @@ impl IntoSystemConfig<()> for SystemConfig { self } + fn on_enter(self, state: impl States) -> SystemSetConfig { + self.in_set(OnEnter(state)); + self + } + + fn on_update(self, state: impl States) -> SystemSetConfig { + self.in_set(OnUpdate(state)); + self + } + + fn on_exit(self, state: impl States) -> SystemSetConfig { + self.in_set(OnExit(state)); + self + } + fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { ambiguous_with(&mut self.graph_info, Box::new(set.into_system_set())); self diff --git a/crates/bevy_ecs/src/scheduling/state.rs b/crates/bevy_ecs/src/scheduling/state.rs index 030a0e4dfb46f..d48be32af9916 100644 --- a/crates/bevy_ecs/src/scheduling/state.rs +++ b/crates/bevy_ecs/src/scheduling/state.rs @@ -53,6 +53,13 @@ pub struct State(pub S); #[derive(Resource, Default)] pub struct NextState(pub Option); +impl NextState { + /// Queue the transition to a new `state`. + pub fn queue(&mut self, state: S) { + self.0 = Some(state); + } +} + /// If a new state is queued in [`NextState`], this system: /// - Takes the new state value from [`NextState`] and updates [`State`]. /// - Runs the [`OnExit(exited_state)`] schedule. diff --git a/examples/ecs/state.rs b/examples/ecs/state.rs index 6ef96e97b968d..64d4b3dbe6188 100644 --- a/examples/ecs/state.rs +++ b/examples/ecs/state.rs @@ -86,7 +86,7 @@ fn setup_menu(mut commands: Commands, asset_server: Res) { } fn menu( - mut state: ResMut>, + mut next_state: ResMut>, mut interaction_query: Query< (&Interaction, &mut BackgroundColor), (Changed, With