From a686708cbc9f5591b9ef36fe0f72915a8777bbd3 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Tue, 15 Nov 2022 12:42:56 -0800 Subject: [PATCH 01/64] Add `bevy_ecs::schedule_v3` module --- crates/bevy_ecs/Cargo.toml | 2 + crates/bevy_ecs/macros/src/lib.rs | 30 +- crates/bevy_ecs/src/lib.rs | 1 + crates/bevy_ecs/src/schedule_v3/condition.rs | 94 ++ crates/bevy_ecs/src/schedule_v3/config.rs | 618 ++++++++++ .../bevy_ecs/src/schedule_v3/executor/mod.rs | 85 ++ .../schedule_v3/executor/multi_threaded.rs | 526 +++++++++ .../src/schedule_v3/executor/simple.rs | 134 +++ .../schedule_v3/executor/single_threaded.rs | 156 +++ crates/bevy_ecs/src/schedule_v3/graph/mod.rs | 2 + .../bevy_ecs/src/schedule_v3/graph/utils.rs | 199 ++++ crates/bevy_ecs/src/schedule_v3/migration.rs | 28 + crates/bevy_ecs/src/schedule_v3/mod.rs | 231 ++++ crates/bevy_ecs/src/schedule_v3/schedule.rs | 1007 +++++++++++++++++ crates/bevy_ecs/src/schedule_v3/set.rs | 149 +++ crates/bevy_ecs/src/schedule_v3/state.rs | 51 + .../src/system/exclusive_function_system.rs | 6 + crates/bevy_ecs/src/system/function_system.rs | 6 + crates/bevy_ecs/src/system/system.rs | 4 + crates/bevy_ecs/src/system/system_piping.rs | 12 + crates/bevy_ecs/src/world/mod.rs | 33 +- crates/bevy_macro_utils/src/lib.rs | 64 ++ crates/bevy_utils/Cargo.toml | 1 + crates/bevy_utils/src/label.rs | 41 + crates/bevy_utils/src/lib.rs | 2 + crates/bevy_utils/src/syncunsafecell.rs | 94 ++ 26 files changed, 3568 insertions(+), 8 deletions(-) create mode 100644 crates/bevy_ecs/src/schedule_v3/condition.rs create mode 100644 crates/bevy_ecs/src/schedule_v3/config.rs create mode 100644 crates/bevy_ecs/src/schedule_v3/executor/mod.rs create mode 100644 crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs create mode 100644 crates/bevy_ecs/src/schedule_v3/executor/simple.rs create mode 100644 crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs create mode 100644 crates/bevy_ecs/src/schedule_v3/graph/mod.rs create mode 100644 crates/bevy_ecs/src/schedule_v3/graph/utils.rs create mode 100644 crates/bevy_ecs/src/schedule_v3/migration.rs create mode 100644 crates/bevy_ecs/src/schedule_v3/mod.rs create mode 100644 crates/bevy_ecs/src/schedule_v3/schedule.rs create mode 100644 crates/bevy_ecs/src/schedule_v3/set.rs create mode 100644 crates/bevy_ecs/src/schedule_v3/state.rs create mode 100644 crates/bevy_utils/src/syncunsafecell.rs diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 000c2121a7d1c..03f368d0f7258 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -28,6 +28,8 @@ fxhash = "0.2" downcast-rs = "1.2" serde = { version = "1", features = ["derive"] } +petgraph = "0.6" + [dev-dependencies] rand = "0.8" diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 3d8a10b3af68e..9f95c63496fe9 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -4,7 +4,9 @@ mod component; mod fetch; use crate::fetch::derive_world_query_impl; -use bevy_macro_utils::{derive_label, get_named_struct_fields, BevyManifest}; +use bevy_macro_utils::{ + derive_label, derive_old_style_label, derive_set, get_named_struct_fields, BevyManifest, +}; use proc_macro::TokenStream; use proc_macro2::Span; use quote::{format_ident, quote}; @@ -565,6 +567,32 @@ pub fn derive_run_criteria_label(input: TokenStream) -> TokenStream { 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 { + 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!("ScheduleLabel").into()); + derive_old_style_label(input, &trait_path) +} + +/// Derive macro generating an impl of the trait `SystemSet`. +#[proc_macro_derive(SystemSet)] +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!("SystemSet").into()); + derive_set(input, &trait_path) +} + pub(crate) fn bevy_ecs_path() -> syn::Path { BevyManifest::default().get_path("bevy_ecs") } diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 47d93beab837d..b0a9f9f155a06 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -14,6 +14,7 @@ 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/condition.rs b/crates/bevy_ecs/src/schedule_v3/condition.rs new file mode 100644 index 0000000000000..f1e0c619f33aa --- /dev/null +++ b/crates/bevy_ecs/src/schedule_v3/condition.rs @@ -0,0 +1,94 @@ +pub use helper::*; + +use crate::system::BoxedSystem; + +pub type BoxedCondition = BoxedSystem<(), bool>; + +/// Functions and closures that convert into [`System`](crate::system::System) +/// trait objects and have [read-only](crate::system::ReadOnlySystemParamFetch) parameters. +pub trait Condition: sealed::Condition {} + +impl Condition for F where F: sealed::Condition {} + +mod sealed { + use crate::system::{ + IntoSystem, IsFunctionSystem, ReadOnlySystemParamFetch, SystemParam, SystemParamFunction, + }; + + pub trait Condition: IntoSystem<(), bool, Params> {} + + impl Condition<(IsFunctionSystem, Params, Marker)> for F + where + F: SystemParamFunction<(), bool, Params, Marker> + Send + Sync + 'static, + Params: SystemParam + 'static, + Params::Fetch: ReadOnlySystemParamFetch, + Marker: 'static, + { + } +} + +pub mod helper { + use crate::schedule_v3::{State, Statelike}; + use crate::system::{Res, Resource}; + + /// Generates a [`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`]-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`]-satisfying closure that returns `true` + /// if the resource exists and is equal to `value`. + 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`]-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`]-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`]-satisfying closure that returns `true` + /// if the state machine exists and is currently in `state`. + 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 new file mode 100644 index 0000000000000..633cea63d0eae --- /dev/null +++ b/crates/bevy_ecs/src/schedule_v3/config.rs @@ -0,0 +1,618 @@ +use bevy_utils::prelude::default; +use bevy_utils::HashSet; + +use crate::{ + schedule_v3::{ + condition::{BoxedCondition, Condition}, + graph::{Ambiguity, DependencyEdgeKind, 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, +} + +/// A [`System`] with scheduling metadata. +pub struct SystemConfig { + pub(super) system: BoxedSystem, + pub(super) graph_info: GraphInfo, + pub(super) conditions: Vec, +} + +pub(super) fn new_set_unchecked(set: BoxedSystemSet) -> SystemSetConfig { + SystemSetConfig { + set, + graph_info: GraphInfo { + sets: HashSet::new(), + dependencies: HashSet::new(), + ambiguous_with: default(), + }, + conditions: Vec::new(), + } +} + +fn new_set(set: BoxedSystemSet) -> SystemSetConfig { + assert!(!set.is_system_type()); + new_set_unchecked(set) +} + +fn new_system(system: BoxedSystem) -> SystemConfig { + // include system in its default sets + let sets = system.default_system_sets().into_iter().collect(); + SystemConfig { + system, + graph_info: GraphInfo { + sets, + dependencies: HashSet::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 (currently not supported)" + ); + + Box::new(condition_system) +} + +/// 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 `set` membership. + fn in_set(self, set: impl SystemSet) -> SystemSetConfig; + /// Run before all members of `set`. + fn before(self, set: impl IntoSystemSet) -> SystemSetConfig; + /// Run after all members of `set`. + fn after(self, set: impl IntoSystemSet) -> SystemSetConfig; + /// Run only if the [`Condition`] is `true` at the time of execution. + fn run_if

(self, condition: impl Condition

) -> SystemSetConfig; + /// Suppress warnings and errors that would result from "ambiguities" with members of `set`. + fn ambiguous_with(self, set: impl SystemSet) -> SystemSetConfig; + /// Suppress warnings and errors that would result from any "ambiguities". + fn ambiguous_with_all(self) -> SystemSetConfig; +} + +impl IntoSystemSetConfig for S +where + S: SystemSet + sealed::IntoSystemSetConfig, +{ + fn into_config(self) -> SystemSetConfig { + new_set(self.dyn_clone()) + } + + fn in_set(self, set: impl SystemSet) -> SystemSetConfig { + new_set(self.dyn_clone()).in_set(set) + } + + fn before(self, set: impl IntoSystemSet) -> SystemSetConfig { + new_set(self.dyn_clone()).before(set) + } + + fn after(self, set: impl IntoSystemSet) -> SystemSetConfig { + new_set(self.dyn_clone()).after(set) + } + + fn run_if

(self, condition: impl Condition

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

(self, condition: impl Condition

) -> SystemSetConfig { + new_set(self).run_if(condition) + } + + fn ambiguous_with(self, set: impl SystemSet) -> SystemSetConfig { + new_set(self).ambiguous_with(set) + } + + fn ambiguous_with_all(self) -> SystemSetConfig { + new_set(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(), "invalid use of system type set"); + self.graph_info.sets.insert(set.dyn_clone()); + self + } + + fn before(mut self, set: impl IntoSystemSet) -> Self { + self.graph_info.dependencies.insert(( + DependencyEdgeKind::Before, + set.into_system_set().dyn_clone(), + )); + self + } + + fn after(mut self, set: impl IntoSystemSet) -> Self { + self.graph_info + .dependencies + .insert((DependencyEdgeKind::After, set.into_system_set().dyn_clone())); + self + } + + fn run_if

(mut self, condition: impl Condition

) -> Self { + self.conditions.push(new_condition(condition)); + self + } + + fn ambiguous_with(mut self, set: impl SystemSet) -> Self { + assert!(!set.is_system_type(), "invalid use of system type set"); + match &mut self.graph_info.ambiguous_with { + detection @ Ambiguity::Check => { + let mut ambiguous_with = HashSet::new(); + ambiguous_with.insert(set.dyn_clone()); + *detection = Ambiguity::IgnoreWithSet(ambiguous_with); + } + Ambiguity::IgnoreWithSet(ambiguous_with) => { + ambiguous_with.insert(set.dyn_clone()); + } + Ambiguity::IgnoreAll => (), + } + + 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 members of `set`. + fn before(self, set: impl IntoSystemSet) -> SystemConfig; + /// Run after all members of `set`. + fn after(self, set: impl IntoSystemSet) -> SystemConfig; + /// Only run if the [`Condition`] is `true` at the time of execution. + fn run_if

(self, condition: impl Condition

) -> SystemConfig; + /// Suppress warnings and errors that would result from "ambiguities" with members of `set`. + fn ambiguous_with(self, set: impl SystemSet) -> SystemConfig; + /// Suppress warnings and errors that would result from any "ambiguities". + fn ambiguous_with_all(self) -> SystemConfig; +} + +impl IntoSystemConfig for F +where + F: IntoSystem<(), (), Params> + sealed::IntoSystemConfig, +{ + fn into_config(self) -> SystemConfig { + new_system(Box::new(IntoSystem::into_system(self))) + } + + fn in_set(self, set: impl SystemSet) -> SystemConfig { + new_system(Box::new(IntoSystem::into_system(self))).in_set(set) + } + + fn before(self, set: impl IntoSystemSet) -> SystemConfig { + new_system(Box::new(IntoSystem::into_system(self))).before(set) + } + + fn after(self, set: impl IntoSystemSet) -> SystemConfig { + new_system(Box::new(IntoSystem::into_system(self))).after(set) + } + + fn run_if

(self, condition: impl Condition

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

(self, condition: impl Condition

) -> SystemConfig { + new_system(self).run_if(condition) + } + + fn ambiguous_with(self, set: impl SystemSet) -> SystemConfig { + new_system(self).ambiguous_with(set) + } + + fn ambiguous_with_all(self) -> SystemConfig { + new_system(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(), "invalid use of system type set"); + self.graph_info.sets.insert(set.dyn_clone()); + self + } + + fn before(mut self, set: impl IntoSystemSet) -> Self { + self.graph_info.dependencies.insert(( + DependencyEdgeKind::Before, + set.into_system_set().dyn_clone(), + )); + self + } + + fn after(mut self, set: impl IntoSystemSet) -> Self { + self.graph_info + .dependencies + .insert((DependencyEdgeKind::After, set.into_system_set().dyn_clone())); + self + } + + fn run_if

(mut self, condition: impl Condition

) -> Self { + self.conditions.push(new_condition(condition)); + self + } + + fn ambiguous_with(mut self, set: impl SystemSet) -> SystemConfig { + assert!(!set.is_system_type(), "invalid use of system type set"); + match &mut self.graph_info.ambiguous_with { + detection @ Ambiguity::Check => { + let mut ambiguous_with = HashSet::new(); + ambiguous_with.insert(set.dyn_clone()); + *detection = Ambiguity::IgnoreWithSet(ambiguous_with); + } + Ambiguity::IgnoreWithSet(ambiguous_with) => { + ambiguous_with.insert(set.dyn_clone()); + } + Ambiguity::IgnoreAll => (), + } + + self + } + + fn ambiguous_with_all(mut self) -> SystemConfig { + 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, + 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 to `set` membership. + fn in_set(self, set: impl SystemSet) -> SystemConfigs { + self.into_configs().in_set(set) + } + + /// Run before all members of `set`. + fn before(self, set: impl IntoSystemSet) -> SystemConfigs { + self.into_configs().before(set) + } + + /// Run after all members of `set`. + fn after(self, set: impl IntoSystemSet) -> SystemConfigs { + self.into_configs().after(set) + } + + /// Treat this collection as a sequence. + /// + /// Ordering constraints will be applied between the successive collection 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(), "invalid use of system type set"); + for config in self.systems.iter_mut() { + config.graph_info.sets.insert(set.dyn_clone()); + } + + self + } + + fn before(mut self, set: impl IntoSystemSet) -> Self { + let set = set.into_system_set(); + for config in self.systems.iter_mut() { + config + .graph_info + .dependencies + .insert((DependencyEdgeKind::Before, set.dyn_clone())); + } + + self + } + + fn after(mut self, set: impl IntoSystemSet) -> Self { + let set = set.into_system_set(); + for config in self.systems.iter_mut() { + config + .graph_info + .dependencies + .insert((DependencyEdgeKind::After, set.dyn_clone())); + } + + self + } + + fn chain(mut self) -> Self { + self.chained = true; + self + } +} + +/// A collection of [`SystemSetConfig`]. +pub struct SystemSetConfigs { + pub(super) sets: Vec, + 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 to `set` membership. + fn in_set(self, set: impl SystemSet) -> SystemSetConfigs { + self.into_configs().in_set(set) + } + + /// Run before all members of `set`. + fn before(self, set: impl IntoSystemSet) -> SystemSetConfigs { + self.into_configs().before(set) + } + + /// Run after all members of `set`. + fn after(self, set: impl IntoSystemSet) -> SystemSetConfigs { + self.into_configs().after(set) + } + + /// Treat this collection as a sequence. + /// + /// Ordering constraints will be applied between the successive collection 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(), "invalid use of system type set"); + for config in self.sets.iter_mut() { + config.graph_info.sets.insert(set.dyn_clone()); + } + + self + } + + fn before(mut self, set: impl IntoSystemSet) -> Self { + let set = set.into_system_set(); + for config in self.sets.iter_mut() { + config + .graph_info + .dependencies + .insert((DependencyEdgeKind::Before, set.dyn_clone())); + } + + self + } + + fn after(mut self, set: impl IntoSystemSet) -> Self { + let set = set.into_system_set(); + for config in self.sets.iter_mut() { + config + .graph_info + .dependencies + .insert((DependencyEdgeKind::After, set.dyn_clone())); + } + + 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, + } + } + } + } +} + +impl_system_collection!(P0, T0, P1, T1); +impl_system_collection!(P0, T0, P1, T1, P2, T2); +impl_system_collection!(P0, T0, P1, T1, P2, T2, P3, T3); +impl_system_collection!(P0, T0, P1, T1, P2, T2, P3, T3, P4, T4); +impl_system_collection!(P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5); +impl_system_collection!(P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6); +impl_system_collection!(P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7); +impl_system_collection!(P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8); +impl_system_collection!( + P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9 +); +impl_system_collection!( + P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10 +); +impl_system_collection!( + P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10, P11, + T11 +); +impl_system_collection!( + P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10, P11, + T11, P12, T12 +); +impl_system_collection!( + P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10, P11, + T11, P12, T12, P13, T13 +); +impl_system_collection!( + P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10, P11, + T11, P12, T12, P13, T13, P14, T14 +); +impl_system_collection!( + P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10, P11, + T11, P12, T12, P13, T13, P14, T14, P15, T15 +); + +impl_system_set_collection!(S0, S1); +impl_system_set_collection!(S0, S1, S2); +impl_system_set_collection!(S0, S1, S2, S3); +impl_system_set_collection!(S0, S1, S2, S3, S4); +impl_system_set_collection!(S0, S1, S2, S3, S4, S5); +impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6); +impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6, S7); +impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6, S7, S8); +impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6, S7, S8, S9); +impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10); +impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11); +impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12); +impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13); +impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14); +impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14, S15); diff --git a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs new file mode 100644 index 0000000000000..1d5830ab0a3d9 --- /dev/null +++ b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs @@ -0,0 +1,85 @@ +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 std::any::{Any, TypeId}; +use std::cell::RefCell; + +use fixedbitset::FixedBitSet; + +use crate::{ + schedule_v3::{BoxedCondition, NodeId}, + system::{BoxedSystem, IntoSystem}, + world::World, +}; + +/// Types that can run a [`SystemSchedule`] on a [`World`]. +pub(super) trait SystemExecutor: Send + Sync { + fn init(&mut self, schedule: &SystemSchedule); + fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World); +} + +/// Controls how a [`Schedule`] will be run. +pub enum ExecutorKind { + /// Runs the schedule using a single thread. + 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. + MultiThreaded, +} + +#[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_deps: Vec<(usize, Vec)>, + pub(super) sets_of_systems: Vec, + pub(super) sets_of_sets: Vec, + pub(super) systems_of_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_deps: Vec::new(), + sets_of_systems: Vec::new(), + sets_of_sets: Vec::new(), + systems_of_sets: Vec::new(), + } + } +} + +// SAFETY: MultiThreadedExecutor does not alias RefCell instances +unsafe impl Sync for SystemSchedule {} + +/// 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`] may change the order buffers are applied. +#[allow(unused_variables)] +pub fn apply_system_buffers(world: &mut World) {} + +/// Returns `true` if the [`System`] is an instance of [`apply_system_buffers`]. +pub(super) fn is_apply_system_buffers(system: &BoxedSystem) -> bool { + fn get_type_id(_: &T) -> TypeId { + TypeId::of::() + } + let type_id = get_type_id(&IntoSystem::into_system(apply_system_buffers)); + (&*system as &dyn Any).type_id() == 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 new file mode 100644 index 0000000000000..5452c3fa9b610 --- /dev/null +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -0,0 +1,526 @@ +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, SystemExecutor, SystemSchedule}, + world::World, +}; + +/// Per-system data used by the [`MultiThreadedExecutor`]. +struct SystemTaskMetadata { + /// Indices of the systems that directly depend on the system. + dependents: Vec, + /// The number of dependencies the system has in total. + dependencies_total: usize, + /// The number of dependencies the system has that have not completed. + dependencies_remaining: usize, + // These values are cached because we can't read them from the system while it's running. + /// The `ArchetypeComponentId` access of the system. + archetype_component_access: Access, + /// 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 { + /// Metadata for scheduling and running system tasks. + system_task_metadata: Vec, + + /// Sends system completion events. + sender: Sender, + /// Receives system completion events. + receiver: Receiver, + /// Scratch vector to avoid frequent allocation. + dependents_scratch: 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, + + /// System sets that have been skipped or had their conditions evaluated. + completed_sets: FixedBitSet, + /// Systems that have run or been skipped. + completed_systems: FixedBitSet, + /// Systems that have no remaining dependencies and are waiting to run. + ready_systems: FixedBitSet, + /// Used to avoid checking systems twice. + seen_ready_systems: FixedBitSet, + /// Systems that are currently running. + running_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 init(&mut self, schedule: &SystemSchedule) { + // pre-allocate space + let sys_count = schedule.system_ids.len(); + let set_count = schedule.set_ids.len(); + + self.completed_sets = FixedBitSet::with_capacity(set_count); + + self.completed_systems = FixedBitSet::with_capacity(sys_count); + self.ready_systems = FixedBitSet::with_capacity(sys_count); + self.seen_ready_systems = FixedBitSet::with_capacity(sys_count); + self.running_systems = FixedBitSet::with_capacity(sys_count); + self.unapplied_systems = FixedBitSet::with_capacity(sys_count); + + self.dependents_scratch = Vec::with_capacity(sys_count); + self.system_task_metadata = Vec::with_capacity(sys_count); + + for index in 0..sys_count { + let (num_dependencies, dependents) = schedule.system_deps[index].clone(); + let system = schedule.systems[index].borrow(); + self.system_task_metadata.push(SystemTaskMetadata { + dependents, + dependencies_total: num_dependencies, + dependencies_remaining: num_dependencies, + is_send: system.is_send(), + is_exclusive: system.is_exclusive(), + archetype_component_access: default(), + }); + } + } + + fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) { + // The start of schedule execution is the best time to do this. + world.check_change_ticks(); + + #[cfg(feature = "trace")] + let _schedule_span = info_span!("schedule").entered(); + ComputeTaskPool::init(TaskPool::default).scope(|scope| { + // the runner itself is a `Send` future so that it can run + // alongside systems that claim the local thread + let runner = async { + // systems with zero dependencies + for (index, system_meta) in self.system_task_metadata.iter_mut().enumerate() { + if system_meta.dependencies_total == 0 { + self.ready_systems.insert(index); + } + } + + // main loop + let world = SyncUnsafeCell::from_mut(world); + while self.completed_systems.count_ones(..) != self.completed_systems.len() { + if !self.exclusive_running { + self.spawn_system_tasks(scope, schedule, world); + } + + if self.running_systems.count_ones(..) > 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() }; + Self::apply_system_buffers(&mut self.unapplied_systems, schedule, world); + + debug_assert_eq!(self.ready_systems.count_ones(..), 0); + debug_assert_eq!(self.running_systems.count_ones(..), 0); + debug_assert_eq!(self.unapplied_systems.count_ones(..), 0); + self.active_access.clear(); + self.completed_sets.clear(); + self.completed_systems.clear(); + }; + + #[cfg(feature = "trace")] + let runner_span = info_span!("schedule_task"); + #[cfg(feature = "trace")] + let runner = runner.instrument(runner_span); + scope.spawn(runner); + }); + } +} + +impl MultiThreadedExecutor { + pub fn new() -> Self { + let (sender, receiver) = async_channel::unbounded(); + Self { + system_task_metadata: Vec::new(), + sender, + receiver, + dependents_scratch: Vec::new(), + active_access: default(), + local_thread_running: false, + exclusive_running: false, + completed_sets: FixedBitSet::new(), + completed_systems: FixedBitSet::new(), + ready_systems: FixedBitSet::new(), + seen_ready_systems: FixedBitSet::new(), + running_systems: FixedBitSet::new(), + unapplied_systems: FixedBitSet::new(), + } + } + + fn spawn_system_tasks<'scope>( + &mut self, + scope: &Scope<'_, 'scope, ()>, + schedule: &'scope SystemSchedule, + world: &'scope SyncUnsafeCell, + ) { + while let Some(system_index) = self + .ready_systems + .difference(&self.seen_ready_systems) + .next() + { + // skip systems we've already seen during this call + self.seen_ready_systems.insert(system_index); + + if self.system_task_metadata[system_index].is_exclusive { + { + // SAFETY: no exclusive system running + let world = unsafe { &*world.get() }; + if !self.can_run(system_index, schedule, world) { + // the `break` here emulates single-threaded runner behavior + // without it, the exclusive system will likely be stalled out + break; + } + if !self.should_run(system_index, schedule, world) { + continue; + } + } + { + // SAFETY: no system running + let world = unsafe { &mut *world.get() }; + self.spawn_exclusive_system_task(scope, system_index, schedule, world); + break; + } + } else { + // SAFETY: no exclusive system running + let world = unsafe { &*world.get() }; + if !self.can_run(system_index, schedule, world) { + continue; + } + if !self.should_run(system_index, schedule, world) { + continue; + } + + self.spawn_system_task(scope, system_index, schedule, world); + } + } + + self.seen_ready_systems.clear(); + } + + fn spawn_system_task<'scope>( + &mut self, + scope: &Scope<'_, 'scope, ()>, + system_index: usize, + schedule: &'scope SystemSchedule, + world: &'scope World, + ) { + // SAFETY: system was not already running + let system = unsafe { &mut *schedule.systems[system_index].as_ptr() }; + + #[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); + + self.ready_systems.set(system_index, false); + self.running_systems.insert(system_index); + + if system_meta.is_send { + scope.spawn(task); + } else { + self.local_thread_running = true; + scope.spawn_on_scope(task); + } + } + + fn spawn_exclusive_system_task<'scope>( + &mut self, + scope: &Scope<'_, 'scope, ()>, + system_index: usize, + schedule: &'scope SystemSchedule, + world: &'scope mut World, + ) { + // SAFETY: system was not already running + let system = unsafe { &mut *schedule.systems[system_index].as_ptr() }; + + #[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(); + + self.ready_systems.set(system_index, false); + self.running_systems.insert(system_index); + self.local_thread_running = true; + self.exclusive_running = true; + + 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(); + Self::apply_system_buffers(&mut unapplied_systems, schedule, 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); + } + } + + fn can_run(&mut self, system_index: usize, schedule: &SystemSchedule, world: &World) -> bool { + #[cfg(feature = "trace")] + let name = schedule.systems[system_index].borrow().name(); + #[cfg(feature = "trace")] + let _span = info_span!("check_access", name = &*name).entered(); + + let system_meta = &mut self.system_task_metadata[system_index]; + if self.local_thread_running && !system_meta.is_send { + // only one thread can access thread-local resources + return false; + } + + let mut system = schedule.systems[system_index].borrow_mut(); + system.update_archetype_component_access(world); + + // TODO: avoid allocation + system_meta.archetype_component_access = system.archetype_component_access().clone(); + let mut total_access = system.archetype_component_access().clone(); + + let mut system_conditions = schedule.system_conditions[system_index].borrow_mut(); + for condition in system_conditions.iter_mut() { + condition.update_archetype_component_access(world); + total_access.extend(condition.archetype_component_access()); + } + + for set_idx in schedule.sets_of_systems[system_index].difference(&self.completed_sets) { + let mut set_conditions = schedule.set_conditions[set_idx].borrow_mut(); + for condition in set_conditions.iter_mut() { + condition.update_archetype_component_access(world); + total_access.extend(condition.archetype_component_access()); + } + } + + total_access.is_compatible(&self.active_access) + } + + fn should_run( + &mut self, + system_index: usize, + schedule: &SystemSchedule, + world: &World, + ) -> bool { + #[cfg(feature = "trace")] + let name = schedule.systems[system_index].borrow().name(); + #[cfg(feature = "trace")] + let _span = info_span!("check_conditions", name = &*name).entered(); + + // evaluate conditions + let mut should_run = true; + + // evaluate set conditions in hierarchical order + for set_idx in schedule.sets_of_systems[system_index].ones() { + if self.completed_sets.contains(set_idx) { + continue; + } + + let mut set_conditions = schedule.set_conditions[set_idx].borrow_mut(); + + // if any condition fails, we need to restore their change ticks + let saved_tick = set_conditions + .iter() + .map(|condition| condition.get_last_change_tick()) + .min(); + + let set_conditions_met = set_conditions.iter_mut().all(|condition| { + #[cfg(feature = "trace")] + let _condition_span = info_span!("condition", name = &*condition.name()).entered(); + // SAFETY: access is compatible + unsafe { condition.run_unsafe((), world) } + }); + + self.completed_sets.insert(set_idx); + + if !set_conditions_met { + // mark all members as completed + for sys_idx in schedule.systems_of_sets[set_idx].ones() { + if !self.completed_systems.contains(sys_idx) { + self.skip_system_and_signal_dependents(sys_idx); + } + } + + self.completed_sets + .union_with(&schedule.sets_of_sets[set_idx]); + + // restore condition change ticks + for condition in set_conditions.iter_mut() { + condition.set_last_change_tick(saved_tick.unwrap()); + } + } + + should_run &= set_conditions_met; + } + + if !should_run { + return false; + } + + let system = schedule.systems[system_index].borrow(); + + // evaluate the system's conditions + let mut system_conditions = schedule.system_conditions[system_index].borrow_mut(); + for condition in system_conditions.iter_mut() { + condition.set_last_change_tick(system.get_last_change_tick()); + } + + let should_run = system_conditions.iter_mut().all(|condition| { + #[cfg(feature = "trace")] + let _condition_span = info_span!("condition", name = &*condition.name()).entered(); + // SAFETY: access is compatible + unsafe { condition.run_unsafe((), world) } + }); + + if !should_run { + self.skip_system_and_signal_dependents(system_index); + return false; + } + + true + } + + fn finish_system_and_signal_dependents(&mut self, system_index: usize) { + if !self.system_task_metadata[system_index].is_send { + self.local_thread_running = false; + } + + if self.system_task_metadata[system_index].is_exclusive { + self.exclusive_running = false; + } + + 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.ready_systems.set(system_index, false); + 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(); + self.dependents_scratch + .extend_from_slice(&self.system_task_metadata[system_index].dependents); + + for &dep_idx in self.dependents_scratch.iter() { + let dependent_meta = &mut self.system_task_metadata[dep_idx]; + dependent_meta.dependencies_remaining -= 1; + if (dependent_meta.dependencies_remaining == 0) + && !self.completed_systems.contains(dep_idx) + { + self.ready_systems.insert(dep_idx); + } + } + + self.dependents_scratch.clear(); + } + + 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, + schedule: &SystemSchedule, + world: &mut World, + ) { + for system_index in unapplied_systems.ones() { + let mut system = schedule.systems[system_index].borrow_mut(); + #[cfg(feature = "trace")] + let _apply_buffers_span = info_span!("apply_buffers", name = &*system.name()).entered(); + system.apply_buffers(world); + } + + unapplied_systems.clear(); + } +} diff --git a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs new file mode 100644 index 0000000000000..70415017f2b28 --- /dev/null +++ b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs @@ -0,0 +1,134 @@ +#[cfg(feature = "trace")] +use bevy_utils::tracing::{info_span, Instrument}; +use fixedbitset::FixedBitSet; + +use crate::{ + schedule_v3::{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 either been evaluated or skipped. + completed_sets: FixedBitSet, + /// Systems that have run or been skipped. + completed_systems: FixedBitSet, +} + +impl SystemExecutor for SimpleExecutor { + fn init(&mut self, schedule: &SystemSchedule) { + let sys_count = schedule.system_ids.len(); + let set_count = schedule.set_ids.len(); + self.completed_sets = FixedBitSet::with_capacity(set_count); + self.completed_systems = FixedBitSet::with_capacity(sys_count); + } + + fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) { + // The start of schedule execution is the best time to do this. + world.check_change_ticks(); + + #[cfg(feature = "trace")] + let _schedule_span = info_span!("schedule").entered(); + for sys_idx in 0..schedule.systems.len() { + if self.completed_systems.contains(sys_idx) { + continue; + } + + #[cfg(feature = "trace")] + let name = schedule.systems[sys_idx].get_mut().name(); + #[cfg(feature = "trace")] + let should_run_span = info_span!("check_conditions", name = &*name).entered(); + + // evaluate conditions + let mut should_run = true; + + // evaluate set conditions in hierarchical order + for set_idx in schedule.sets_of_systems[sys_idx].ones() { + if self.completed_sets.contains(set_idx) { + continue; + } + + let set_conditions = schedule.set_conditions[set_idx].get_mut(); + + // if any condition fails, we need to restore their change ticks + let saved_tick = set_conditions + .iter() + .map(|condition| condition.get_last_change_tick()) + .min(); + + let set_conditions_met = set_conditions.iter_mut().all(|condition| { + #[cfg(feature = "trace")] + let _condition_span = + info_span!("condition", name = &*condition.name()).entered(); + condition.run((), world) + }); + + self.completed_sets.insert(set_idx); + + if !set_conditions_met { + // mark all members as completed + self.completed_systems + .union_with(&schedule.systems_of_sets[set_idx]); + self.completed_sets + .union_with(&schedule.sets_of_sets[set_idx]); + + // restore condition change ticks + for condition in set_conditions.iter_mut() { + condition.set_last_change_tick(saved_tick.unwrap()); + } + } + + should_run &= set_conditions_met; + } + + if !should_run { + continue; + } + + let system = schedule.systems[sys_idx].get_mut(); + + // evaluate the system's conditions + let system_conditions = schedule.system_conditions[sys_idx].get_mut(); + for condition in system_conditions.iter_mut() { + condition.set_last_change_tick(system.get_last_change_tick()); + } + + let should_run = system_conditions.iter_mut().all(|condition| { + #[cfg(feature = "trace")] + let _condition_span = info_span!("condition", name = &*condition.name()).entered(); + condition.run((), world) + }); + + #[cfg(feature = "trace")] + should_run_span.exit(); + + // mark system as completed regardless + self.completed_systems.insert(sys_idx); + + if !should_run { + continue; + } + + #[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); + } + } +} + +impl SimpleExecutor { + pub const fn new() -> Self { + Self { + completed_sets: FixedBitSet::new(), + completed_systems: FixedBitSet::new(), + } + } +} diff --git a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs new file mode 100644 index 0000000000000..e768abf9f3e9a --- /dev/null +++ b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs @@ -0,0 +1,156 @@ +#[cfg(feature = "trace")] +use bevy_utils::tracing::{info_span, Instrument}; +use fixedbitset::FixedBitSet; + +use crate::{ + schedule_v3::{is_apply_system_buffers, SystemExecutor, SystemSchedule}, + world::World, +}; + +/// Runs the schedule using a single thread. +#[derive(Default)] +pub struct SingleThreadedExecutor { + /// System sets whose conditions have either been evaluated or skipped. + completed_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 init(&mut self, schedule: &SystemSchedule) { + // pre-allocate space + let sys_count = schedule.system_ids.len(); + let set_count = schedule.set_ids.len(); + self.completed_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) { + // The start of schedule execution is the best time to do this. + world.check_change_ticks(); + + #[cfg(feature = "trace")] + let _schedule_span = info_span!("schedule").entered(); + for sys_idx in 0..schedule.systems.len() { + if self.completed_systems.contains(sys_idx) { + continue; + } + + #[cfg(feature = "trace")] + let name = schedule.systems[sys_idx].get_mut().name(); + #[cfg(feature = "trace")] + let should_run_span = info_span!("check_conditions", name = &*name).entered(); + + // evaluate conditions + let mut should_run = true; + + // evaluate set conditions in hierarchical order + for set_idx in schedule.sets_of_systems[sys_idx].ones() { + if self.completed_sets.contains(set_idx) { + continue; + } + + let set_conditions = schedule.set_conditions[set_idx].get_mut(); + + // if any condition fails, we need to restore their change ticks + let saved_tick = set_conditions + .iter() + .map(|condition| condition.get_last_change_tick()) + .min(); + + let set_conditions_met = set_conditions.iter_mut().all(|condition| { + #[cfg(feature = "trace")] + let _condition_span = + info_span!("condition", name = &*condition.name()).entered(); + condition.run((), world) + }); + + self.completed_sets.insert(set_idx); + + if !set_conditions_met { + // mark all members as completed + self.completed_systems + .union_with(&schedule.systems_of_sets[set_idx]); + self.completed_sets + .union_with(&schedule.sets_of_sets[set_idx]); + + // restore condition change ticks + for condition in set_conditions.iter_mut() { + condition.set_last_change_tick(saved_tick.unwrap()); + } + } + + should_run &= set_conditions_met; + } + + if !should_run { + continue; + } + + let system = schedule.systems[sys_idx].get_mut(); + + // evaluate the system's conditions + let system_conditions = schedule.system_conditions[sys_idx].get_mut(); + for condition in system_conditions.iter_mut() { + condition.set_last_change_tick(system.get_last_change_tick()); + } + + let should_run = system_conditions.iter_mut().all(|condition| { + #[cfg(feature = "trace")] + let _condition_span = info_span!("condition", name = &*condition.name()).entered(); + condition.run((), world) + }); + + #[cfg(feature = "trace")] + should_run_span.exit(); + + // mark system as completed regardless + self.completed_systems.insert(sys_idx); + + if !should_run { + continue; + } + + 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.set(sys_idx, true); + } + } + + self.apply_system_buffers(schedule, world); + } +} + +impl SingleThreadedExecutor { + pub const fn new() -> Self { + Self { + completed_sets: FixedBitSet::new(), + completed_systems: FixedBitSet::new(), + unapplied_systems: FixedBitSet::new(), + } + } + + fn apply_system_buffers(&mut self, schedule: &mut SystemSchedule, world: &mut World) { + for sys_idx in self.unapplied_systems.ones() { + let system = schedule.systems[sys_idx].get_mut(); + #[cfg(feature = "trace")] + let _apply_buffers_span = info_span!("apply_buffers", name = &*system.name()).entered(); + system.apply_buffers(world); + } + + self.unapplied_systems.clear(); + } +} diff --git a/crates/bevy_ecs/src/schedule_v3/graph/mod.rs b/crates/bevy_ecs/src/schedule_v3/graph/mod.rs new file mode 100644 index 0000000000000..280f9fc05b67f --- /dev/null +++ b/crates/bevy_ecs/src/schedule_v3/graph/mod.rs @@ -0,0 +1,2 @@ +mod utils; +pub use utils::*; diff --git a/crates/bevy_ecs/src/schedule_v3/graph/utils.rs b/crates/bevy_ecs/src/schedule_v3/graph/utils.rs new file mode 100644 index 0000000000000..77469c0d04ae4 --- /dev/null +++ b/crates/bevy_ecs/src/schedule_v3/graph/utils.rs @@ -0,0 +1,199 @@ +use crate::schedule_v3::set::*; +use bevy_utils::{HashMap, HashSet}; + +use fixedbitset::FixedBitSet; +use petgraph::{graphmap::NodeTrait, prelude::*}; + +use std::fmt::Debug; + +/// Unique identifier for a system or system set. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) enum NodeId { + System(u64), + Set(u64), +} + +impl NodeId { + /// 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 inserted in the dependency graph. +#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub(crate) enum DependencyEdgeKind { + /// A node that should be preceded. + Before, + /// A node that should be succeeded. + After, +} + +/// 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. + IgnoreWithSet(HashSet), + /// Ignore all warnings. + IgnoreAll, +} + +#[derive(Clone)] +pub(crate) struct GraphInfo { + pub(crate) sets: HashSet, + pub(crate) dependencies: HashSet<(DependencyEdgeKind, BoxedSystemSet)>, + pub(crate) ambiguous_with: Ambiguity, +} + +#[derive(Clone)] +pub(crate) struct IndexedGraphInfo { + pub(crate) sets: HashSet, + pub(crate) edges: HashSet<(DependencyEdgeKind, NodeId)>, +} + +/// 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) +} + +pub(crate) struct CheckGraphResults { + // 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) not_connected: HashSet<(V, V)>, + // Edges that are redundant because a longer path exists. + pub(crate) transitive_edges: Vec<(V, V)>, + // Boolean reachability matrix for the graph. + pub(crate) reachable: FixedBitSet, + // Variant of the graph with the fewest possible edges. + pub(crate) tred: DiGraphMap, + // Variant of the graph with the most possible edges. + pub(crate) tcls: DiGraphMap, +} + +impl Default for CheckGraphResults { + fn default() -> Self { + Self { + connected: HashSet::new(), + not_connected: HashSet::new(), + transitive_edges: Vec::new(), + reachable: FixedBitSet::new(), + tred: DiGraphMap::new(), + tcls: DiGraphMap::new(), + } + } +} + +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(); + let mut map = HashMap::with_capacity(n); + let mut tsorted = DiGraphMap::::new(); + // iterate nodes in topological order + for (i, &node) in topological_order.iter().enumerate() { + map.insert(node, i); + tsorted.add_node(node.clone()); + // insert nodes as successors to their predecessors + for pred in graph.neighbors_directed(node, Direction::Incoming) { + tsorted.add_edge(pred, node, ()); + } + } + + let mut tred = DiGraphMap::::new(); + let mut tcls = DiGraphMap::::new(); + + let mut connected = HashSet::new(); + let mut not_connected = HashSet::new(); + let mut transitive_edges = Vec::new(); + + let mut visited = FixedBitSet::with_capacity(n); + let mut reachable = FixedBitSet::with_capacity(n * n); + + // iterate nodes in topological order + for node in tsorted.nodes() { + tred.add_node(node); + tcls.add_node(node); + } + + // iterate nodes in reverse topological order + for a in tsorted.nodes().rev() { + let index_a = *map.get(&a).unwrap(); + // iterate their successors in topological order + for b in tsorted.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 + tred.add_edge(a, b, ()); + tcls.add_edge(a, b, ()); + reachable.set(index(index_a, index_b, n), true); + + let successors = tcls + .neighbors_directed(b, Direction::Outgoing) + .collect::>(); + for c in successors.into_iter() { + let index_c = *map.get(&c).unwrap(); + debug_assert!(index_b < index_c); + if !visited[index_c] { + visited.set(index_c, true); + tcls.add_edge(a, c, ()); + reachable.set(index(index_a, index_c, n), true); + } + } + } else { + transitive_edges.push((a, b)); + } + } + + visited.clear(); + } + + for i in 0..(n - 1) { + // reachable is upper triangular because the nodes are in topological order + 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] { + not_connected.insert(pair); + } else { + connected.insert(pair); + } + } + } + + // Fill diagonal. + for i in 0..n { + reachable.set(index(i, i, n), true); + } + + CheckGraphResults { + connected, + not_connected, + transitive_edges, + reachable, + tred, + tcls, + } +} diff --git a/crates/bevy_ecs/src/schedule_v3/migration.rs b/crates/bevy_ecs/src/schedule_v3/migration.rs new file mode 100644 index 0000000000000..0587363404f93 --- /dev/null +++ b/crates/bevy_ecs/src/schedule_v3/migration.rs @@ -0,0 +1,28 @@ +use crate::schedule_v3::*; +use crate::world::World; + +/// New "stageless" [`App`](bevy_app::App) methods. +pub trait AppExt { + fn set_default_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self; + fn edit_schedule( + &mut self, + label: impl ScheduleLabel, + f: impl FnMut(&mut Schedule), + ) -> &mut Self; +} + +/// New "stageless" [`World`] methods. +pub trait WorldExt { + /// Runs the [`Schedule`] associated with the provided [`ScheduleLabel`]. + fn run_schedule(&mut self, label: impl ScheduleLabel); +} + +impl WorldExt for World { + fn run_schedule(&mut self, label: impl ScheduleLabel) { + let mut schedule = self.resource_mut::().check_out(&label).unwrap(); + schedule.run(self); + self.resource_mut::() + .check_in(&label, schedule) + .unwrap(); + } +} diff --git a/crates/bevy_ecs/src/schedule_v3/mod.rs b/crates/bevy_ecs/src/schedule_v3/mod.rs new file mode 100644 index 0000000000000..dd2ca9a7f0613 --- /dev/null +++ b/crates/bevy_ecs/src/schedule_v3/mod.rs @@ -0,0 +1,231 @@ +#![allow(warnings)] +mod condition; +mod config; +mod executor; +mod graph; +mod migration; +mod schedule; +mod set; +mod state; + +pub use self::condition::*; +pub use self::config::*; +pub use self::executor::*; +use self::graph::*; +pub use self::migration::*; +pub use self::schedule::*; +pub use self::set::*; +pub use self::state::*; + +#[cfg(test)] +mod tests { + use super::*; + use crate as bevy_ecs; + use crate::system::*; + use crate::world::World; + + #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] + enum TestSchedule { + A, + B, + C, + D, + X, + } + + #[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] + enum TestSet { + A, + B, + C, + D, + X, + } + + #[test] + fn correct_order() { + #[derive(Resource)] + struct X(Vec); + + let mut world = World::new(); + world.insert_resource(X(Vec::new())); + + fn run(set: TestSet) -> impl FnMut(ResMut) { + move |mut x: ResMut| { + x.0.push(set.clone()); + } + } + + let mut schedule = Schedule::new(); + schedule.add_systems( + ( + run(TestSet::A), + run(TestSet::B), + run(TestSet::C), + run(TestSet::D), + ) + .chain(), + ); + + schedule.run(&mut world); + let X(results) = world.remove_resource::().unwrap(); + assert_eq!( + results, + vec![TestSet::A, TestSet::B, TestSet::C, TestSet::D] + ); + } + + #[test] + #[should_panic] + fn dependency_loop() { + // TODO: error on cross dependencies to avoid loops after flattening + let mut world = World::new(); + 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(BuildError::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(BuildError::DependencyCycle))); + } + + #[test] + #[should_panic] + fn hierarchy_loop() { + let mut world = World::new(); + 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(BuildError::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!(matches!(result, 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(BuildError::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_conflict() { + let mut world = World::new(); + let mut schedule = Schedule::new(); + + // 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(BuildError::HierarchyConflict))); + } + + #[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(BuildError::CrossDependency(_, _)))); + } + + #[test] + fn ambiguity() { + #[derive(Resource)] + struct X; + + fn res_ref(x: Res) {} + fn res_mut(mut x: ResMut) {} + + let mut world = World::new(); + let mut schedule = Schedule::new(); + + schedule.add_systems((res_ref, res_mut)); + let result = schedule.initialize(&mut world); + assert!(matches!(result, Err(BuildError::Ambiguity))); + } + + #[test] + fn schedule_already_exists() { + let mut schedules = Schedules::new(); + let result = schedules.insert(TestSchedule::X, Schedule::new()); + assert!(matches!(result, Ok(()))); + + let result = schedules.insert(TestSchedule::X, Schedule::new()); + assert!(matches!(result, Err(InsertionError::AlreadyExists(_)))); + } +} diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs new file mode 100644 index 0000000000000..e947bde189982 --- /dev/null +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -0,0 +1,1007 @@ +use std::{ + borrow::Cow, + fmt::{Debug, Write}, + result::Result, +}; + +use bevy_utils::{ + thiserror::Error, + tracing::{error, warn}, + HashMap, HashSet, +}; + +use fixedbitset::FixedBitSet; +use petgraph::graph::Node; +use petgraph::{algo::tarjan_scc, prelude::*}; + +use crate::component::ComponentId; +use crate::{ + self as bevy_ecs, + schedule_v3::*, + system::{BoxedSystem, Resource}, + world::World, +}; + +/// Stores [`ScheduleLabel`]-[`Schedule`] pairs. +#[derive(Default, Resource)] +pub struct Schedules { + inner: HashMap>, + default: Option, +} + +impl Schedules { + pub fn new() -> Self { + Self { + inner: HashMap::new(), + default: None, + } + } + + pub(crate) fn set_default(&mut self, label: impl ScheduleLabel) { + self.default = Some(label.dyn_clone()); + } + + /// Insert a new labeled schedule into the map. + /// + /// # Errors + /// + /// TODO + pub fn insert( + &mut self, + label: impl ScheduleLabel, + schedule: Schedule, + ) -> Result<(), InsertionError> { + let label = label.dyn_clone(); + if self.inner.contains_key(&label) { + return Err(InsertionError::AlreadyExists(label)); + } + self.inner.insert(label, Some(schedule)); + Ok(()) + } + + /// Returns the schedule corresponding to the label. + /// + /// # Errors + /// + /// TODO + pub(crate) fn check_out( + &mut self, + label: &dyn ScheduleLabel, + ) -> Result { + let label = label.dyn_clone(); + match self.inner.get_mut(&label) { + Some(container) => match container.take() { + Some(schedule) => Ok(schedule), + None => Err(ExtractionError::AlreadyExtracted(label)), + }, + None => Err(ExtractionError::Unknown(label)), + } + } + + /// Re-inserts the schedule corresponding to the label into the map. + /// + /// The schedule must have been previously extracted using [`check_out`](#method.check_out). + /// + /// # Errors + /// + /// TODO + pub(crate) fn check_in( + &mut self, + label: &dyn ScheduleLabel, + schedule: Schedule, + ) -> Result<(), ExtractionError> { + let label = label.dyn_clone(); + match self.inner.get_mut(&label) { + Some(container) => match container.take() { + Some(_) => Err(ExtractionError::NotExtracted(label)), + None => { + *container = Some(schedule); + Ok(()) + } + }, + None => Err(ExtractionError::Unknown(label)), + } + } + + /// Iterates all system change ticks and clamps any older than [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE). + /// This prevents overflow and thus prevents false positives. + /// + /// **Note:** Does nothing if the [`World`] counter has not been incremented at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD) + /// times since the previous pass. + pub(crate) fn check_change_ticks(&mut self, change_tick: u32, last_check_tick: u32) { + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!("check ticks").entered(); + for schedule in self.inner.values_mut().flatten() { + schedule.check_change_ticks(change_tick, last_check_tick); + } + } +} + +/// TBD +pub struct Schedule { + graph: ScheduleMeta, + executable: SystemSchedule, + executor: Box, +} + +impl Schedule { + pub fn new() -> Self { + Self { + graph: ScheduleMeta::new(), + executable: SystemSchedule::new(), + executor: Box::new(MultiThreadedExecutor::new()), + } + } + + pub fn add_system

(&mut self, system: impl IntoSystemConfig

) { + self.graph.add_system(system); + } + + pub fn add_systems

(&mut self, systems: impl IntoSystemConfigs

) { + self.graph.add_systems(systems); + } + + pub fn configure_set(&mut self, set: impl IntoSystemSetConfig) { + self.graph.configure_set(set); + } + + pub fn configure_sets(&mut self, sets: impl IntoSystemSetConfigs) { + self.graph.configure_sets(sets); + } + + pub fn set_default_set(&mut self, set: impl SystemSet) { + self.graph.set_default_set(set); + } + + pub fn set_executor(&mut self, executor: ExecutorKind) { + self.executor = match executor { + ExecutorKind::Simple => Box::new(SimpleExecutor::new()), + ExecutorKind::SingleThreaded => Box::new(SingleThreadedExecutor::new()), + ExecutorKind::MultiThreaded => Box::new(MultiThreadedExecutor::new()), + }; + } + + pub fn run(&mut self, world: &mut World) { + self.initialize(world).unwrap(); + self.executor.run(&mut self.executable, world); + } + + pub(crate) fn initialize(&mut self, world: &mut World) -> Result<(), BuildError> { + if self.graph.changed { + self.graph.initialize(world); + self.graph.update_schedule(&mut self.executable)?; + self.executor.init(&self.executable); + } + + Ok(()) + } + + /// Iterates all component change ticks and clamps any older than [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE). + /// This prevents overflow and thus prevents false positives. + /// + /// **Note:** Does nothing if the [`World`] counter has not been incremented at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD) + /// times since the previous pass. + pub(crate) fn check_change_ticks(&mut self, change_tick: u32, last_change_tick: u32) { + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!("check schedule ticks").entered(); + todo!(); + } +} + +#[derive(Default)] +struct DAG { + /// A directed graph. + graph: DiGraphMap, + /// A cached topological ordering of the graph. + topsort: Vec, +} + +impl DAG { + pub fn new() -> Self { + Self { + graph: DiGraphMap::new(), + topsort: Vec::new(), + } + } +} + +struct SystemSetMeta(BoxedSystemSet); + +impl SystemSetMeta { + pub fn new(set: BoxedSystemSet) -> Self { + Self(set) + } + + pub fn name(&self) -> Cow<'static, str> { + format!("{:?}", &self.0).into() + } + + pub fn is_system_type(&self) -> bool { + self.0.is_system_type() + } +} + +enum UninitNode { + System(BoxedSystem, Vec), + SystemSet(Vec), +} + +#[derive(Default)] +struct ScheduleMeta { + system_set_ids: HashMap, + system_sets: HashMap, + systems: HashMap, + conditions: HashMap>, + + hierarchy: DAG, + dependency: DAG, + dependency_flattened: DAG, + + ambiguous_with: UnGraphMap, + ambiguous_with_flattened: UnGraphMap, + ambiguous_with_all: HashSet, + + default_set: Option, + next_node_id: u64, + changed: bool, + uninit: Vec<(NodeId, UninitNode)>, +} + +impl ScheduleMeta { + pub fn new() -> Self { + Self { + system_set_ids: HashMap::new(), + system_sets: HashMap::new(), + systems: HashMap::new(), + conditions: HashMap::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, + next_node_id: 0, + changed: false, + uninit: Vec::new(), + } + } + + fn next_id(&mut self) -> u64 { + let id = self.next_node_id; + self.next_node_id = self.next_node_id.checked_add(1).unwrap(); + id + } + + fn set_default_set(&mut self, set: impl SystemSet) { + assert!(!set.is_system_type(), "invalid use of system type set"); + self.default_set = Some(set.dyn_clone()); + } + + fn add_systems

(&mut self, systems: impl IntoSystemConfigs

) { + let SystemConfigs { systems, chained } = systems.into_configs(); + let mut iter = systems + .into_iter() + .map(|system| self.add_system_inner(system).unwrap()); + + if chained { + let ids = iter.collect::>(); + self.chain(ids); + } else { + while iter.next().is_some() {} + } + } + + 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.next_id()); + + if graph_info.sets.is_empty() { + if let Some(default) = self.default_set.as_ref() { + graph_info.sets.insert(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, UninitNode::System(system, conditions))); + self.changed = true; + + Ok(id) + } + + fn configure_sets(&mut self, sets: impl IntoSystemSetConfigs) { + let SystemSetConfigs { sets, chained } = sets.into_configs(); + let mut iter = sets + .into_iter() + .map(|set| self.configure_set_inner(set).unwrap()); + + if chained { + let ids = iter.collect::>(); + self.chain(ids); + } else { + while iter.next().is_some() {} + } + } + + 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), + }; + + // TODO: only if this is the first time configure_set has been called for this set + if graph_info.sets.is_empty() { + if let Some(default) = self.default_set.as_ref() { + graph_info.sets.insert(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, UninitNode::SystemSet(conditions))); + self.changed = true; + + Ok(id) + } + + fn add_set(&mut self, set: BoxedSystemSet) -> NodeId { + let id = NodeId::Set(self.next_id()); + self.system_set_ids.insert(set.dyn_clone(), id); + self.system_sets.insert(id, SystemSetMeta::new(set)); + id + } + + fn chain(&mut self, nodes: Vec) { + for pair in nodes.as_slice().windows(2) { + self.dependency.graph.add_edge(pair[0], pair[1], ()); + } + } + + fn check_sets(&mut self, id: &NodeId, graph_info: &GraphInfo) -> Result<(), BuildError> { + for set in graph_info.sets.iter() { + match self.system_set_ids.get(set) { + Some(set_id) => { + if id == set_id { + return Err(BuildError::HierarchyLoop(set.dyn_clone())); + } + } + None => { + self.add_set(set.dyn_clone()); + } + } + } + + Ok(()) + } + + fn check_edges(&mut self, id: &NodeId, graph_info: &GraphInfo) -> Result<(), BuildError> { + for (_, other) in graph_info.dependencies.iter() { + match self.system_set_ids.get(other) { + Some(other_id) => { + if id == other_id { + return Err(BuildError::DependencyLoop(other.dyn_clone())); + } + } + None => { + self.add_set(other.dyn_clone()); + } + } + } + + if let Ambiguity::IgnoreWithSet(ambiguous_with) = &graph_info.ambiguous_with { + for other in ambiguous_with.iter() { + match self.system_set_ids.get(other) { + Some(other_id) => { + if id == other_id { + return Err(BuildError::DependencyLoop(other.dyn_clone())); + } + } + None => { + self.add_set(other.dyn_clone()); + } + } + } + } + + Ok(()) + } + + fn update_graphs(&mut self, id: NodeId, graph_info: GraphInfo) -> Result<(), BuildError> { + self.check_sets(&id, &graph_info)?; + self.check_edges(&id, &graph_info)?; + + 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 (edge_kind, other) in dependencies + .into_iter() + .map(|(edge_kind, set)| (edge_kind, self.system_set_ids[&set])) + { + let (lhs, rhs) = match edge_kind { + DependencyEdgeKind::Before => (id, other), + DependencyEdgeKind::After => (other, id), + }; + self.dependency.graph.add_edge(lhs, rhs, ()); + } + + match ambiguous_with { + Ambiguity::Check => (), + Ambiguity::IgnoreWithSet(ambigous_with) => { + for (other) in ambigous_with + .into_iter() + .map(|set| self.system_set_ids[&set]) + { + self.ambiguous_with.add_edge(id, other, ()); + } + } + Ambiguity::IgnoreAll => { + self.ambiguous_with_all.insert(id); + } + } + + Ok(()) + } + + fn initialize(&mut self, world: &mut World) { + for (id, uninit) in self.uninit.drain(..) { + match uninit { + UninitNode::System(mut system, mut conditions) => { + debug_assert!(id.is_system()); + system.initialize(world); + self.systems.insert(id, system); + for condition in conditions.iter_mut() { + condition.initialize(world); + } + self.conditions.insert(id, conditions); + } + UninitNode::SystemSet(mut conditions) => { + debug_assert!(id.is_set()); + for condition in conditions.iter_mut() { + condition.initialize(world); + } + self.conditions + .entry(id) + .or_insert_with(Vec::new) + .append(&mut conditions); + } + } + } + } + + fn build_dependency_graph(&mut self) -> Result<(), BuildError> { + // 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(BuildError::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); + return Err(BuildError::HierarchyConflict); + } + + // 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(BuildError::DependencyCycle); + } + + self.dependency.topsort = dep_scc.into_iter().flatten().rev().collect::>(); + + 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(BuildError::CrossDependency(name_a.into(), name_b.into())); + } + } + + // map system sets to all their member systems + let mut systems = 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.insert(set, Vec::new()); + + for child in self + .hierarchy + .graph + .neighbors_directed(set, Direction::Outgoing) + { + match child { + NodeId::System(_) => { + systems.get_mut(&set).unwrap().push(child); + } + NodeId::Set(_) => { + let [sys, child_sys] = systems.get_many_mut([&set, &child]).unwrap(); + sys.extend_from_slice(child_sys); + } + } + } + } + + // system type sets with multiple members cannot be used in dependencies + for (&set, systems) in systems.iter() { + if self.system_sets[&set].is_system_type() { + let mut dep_count = 0; + dep_count += self + .dependency + .graph + .edges_directed(set, Direction::Incoming) + .count(); + dep_count += self + .dependency + .graph + .edges_directed(set, Direction::Outgoing) + .count(); + + if systems.len() > 1 && dep_count > 0 { + let type_set = self.system_sets[&set].0.dyn_clone(); + return Err(BuildError::SystemTypeSetAmbiguity(type_set)); + } + } + } + + // 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[&lhs].iter() { + dependency_flattened.add_edge(lhs_, rhs, ()); + } + } + (NodeId::System(_), NodeId::Set(_)) => { + for &rhs_ in systems[&rhs].iter() { + dependency_flattened.add_edge(lhs, rhs_, ()); + } + } + (NodeId::Set(_), NodeId::Set(_)) => { + for &lhs_ in systems[&lhs].iter() { + for &rhs_ in systems[&rhs].iter() { + 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(BuildError::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, + ); + + // flatten 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[&lhs].iter() { + ambiguous_with_flattened.add_edge(lhs_, rhs, ()); + } + } + (NodeId::System(_), NodeId::Set(_)) => { + for &rhs_ in systems[&rhs].iter() { + ambiguous_with_flattened.add_edge(lhs, rhs_, ()); + } + } + (NodeId::Set(_), NodeId::Set(_)) => { + for &lhs_ in systems[&lhs].iter() { + for &rhs_ in systems[&rhs].iter() { + ambiguous_with_flattened.add_edge(lhs_, rhs_, ()); + } + } + } + } + } + + self.ambiguous_with_flattened = ambiguous_with_flattened; + + let mut conflicting_systems = Vec::new(); + for &(a, b) in flat_results.not_connected.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.get(&a).unwrap(); + let system_b = self.systems.get(&b).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); + return Err(BuildError::Ambiguity); + } + + Ok(()) + } + + fn build_schedule(&mut self) -> SystemSchedule { + let sys_count = self.systems.len(); + let node_count = self.systems.len() + self.system_sets.len(); + + 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::>(); + + // get the number of dependencies and the immediate dependents of each system + // (needed to run systems in the correct order) + let mut system_deps = Vec::with_capacity(sys_count); + for &sys_id in dg_system_ids.iter() { + 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_deps.push((num_dependencies, dependents)); + } + + let hg_systems = self + .hierarchy + .topsort + .iter() + .filter(|&id| id.is_system()) + .cloned() + .enumerate() + .collect::>(); + + let (hg_set_idxs, hg_set_ids): (Vec<_>, Vec<_>) = self + .hierarchy + .topsort + .iter() + .filter(|&id| { + // ignore system type sets + // ignore system sets that have no conditions + id.is_set() && self.conditions.get(id).filter(|v| !v.is_empty()).is_some() + }) + .cloned() + .enumerate() + .unzip(); + + let set_count = hg_set_ids.len(); + let result = check_graph(&self.hierarchy.graph, &self.hierarchy.topsort); + + // get the rows and columns of the hierarchy graph's reachability matrix + // (needed to we can evaluate conditions in the correct order) + let mut sets_of_sets = vec![FixedBitSet::with_capacity(set_count); set_count]; + for (i, &row) in hg_set_idxs.iter().enumerate() { + let bitset = &mut sets_of_sets[i]; + for (idx, &col) in hg_set_idxs.iter().enumerate() { + let is_descendant = result.reachable[index(row, col, node_count)]; + bitset.set(idx, is_descendant); + } + } + + let mut systems_of_sets = vec![FixedBitSet::with_capacity(sys_count); set_count]; + for (i, &row) in hg_set_idxs.iter().enumerate() { + let bitset = &mut systems_of_sets[i]; + for &(col, sys_id) in hg_systems.iter() { + let idx = dg_system_idx_map[&sys_id]; + let is_descendant = result.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.iter() { + 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 = result.reachable[index(row, col, node_count)]; + bitset.set(idx, is_ancestor); + } + } + + 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_deps, + sets_of_systems, + sets_of_sets, + systems_of_sets, + } + } + + fn update_schedule(&mut self, schedule: &mut SystemSchedule) -> Result<(), BuildError> { + use std::cell::RefCell; + + if !self.uninit.is_empty() { + return Err(BuildError::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.insert(id, system.into_inner()); + self.conditions.insert(id, conditions.into_inner()); + } + + for (id, conditions) in schedule + .set_ids + .drain(..) + .zip(schedule.set_conditions.drain(..)) + { + self.conditions.insert(id, conditions.into_inner()); + } + + self.build_dependency_graph()?; + *schedule = self.build_schedule(); + + // move systems into new schedule + for &id in schedule.system_ids.iter() { + let system = self.systems.remove(&id).unwrap(); + let conditions = self.conditions.remove(&id).unwrap(); + schedule.systems.push(RefCell::new(system)); + schedule.system_conditions.push(RefCell::new(conditions)); + } + + for &id in schedule.set_ids.iter() { + let conditions = self.conditions.remove(&id).unwrap(); + schedule.set_conditions.push(RefCell::new(conditions)); + } + + Ok(()) + } +} + +impl ScheduleMeta { + fn get_node_name(&self, id: &NodeId) -> Cow<'static, str> { + match id { + NodeId::System(_) => self.systems[id].name(), + NodeId::Set(_) => self.system_sets[id].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)]) { + // TODO: warn but fix with transitive reduction + let mut message = String::from("hierarchy contains redundant edge(s)"); + for (parent, child) in transitive_edges.iter() { + 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.iter() { + 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); + } +} + +#[derive(Error, Debug)] +#[non_exhaustive] +pub enum InsertionError { + #[error("schedule `{0:?}` already exists")] + AlreadyExists(BoxedScheduleLabel), +} + +#[derive(Error, Debug)] +#[non_exhaustive] +pub enum ExtractionError { + #[error("unknown schedule: `{0:?}`")] + Unknown(BoxedScheduleLabel), + #[error("schedule `{0:?}` is not available")] + AlreadyExtracted(BoxedScheduleLabel), + #[error("schedule `{0:?}` has not been extracted")] + NotExtracted(BoxedScheduleLabel), +} + +#[derive(Error, Debug)] +#[non_exhaustive] +pub enum BuildError { + #[error("`{0:?}` contains itself")] + HierarchyLoop(BoxedSystemSet), + #[error("system set hierarchy contains cycle(s)")] + HierarchyCycle, + #[error("system set hierarchy contains conflicting relationships")] + HierarchyConflict, + #[error("`{0:?}` depends on itself")] + DependencyLoop(BoxedSystemSet), + #[error("dependencies contain cycle(s)")] + DependencyCycle, + #[error("`{0:?}` and `{1:?}` can have a hierarchical OR dependent relationship, not both")] + CrossDependency(String, String), + #[error("ambiguous dependency on `{0:?}`, multiple instances of this system exist")] + SystemTypeSetAmbiguity(BoxedSystemSet), + #[error("systems with conflicting access have indeterminate execution order")] + Ambiguity, + #[error("schedule not initialized")] + Uninitialized, +} diff --git a/crates/bevy_ecs/src/schedule_v3/set.rs b/crates/bevy_ecs/src/schedule_v3/set.rs new file mode 100644 index 0000000000000..6ccff2745b3ec --- /dev/null +++ b/crates/bevy_ecs/src/schedule_v3/set.rs @@ -0,0 +1,149 @@ +use std::any::TypeId; +use std::fmt::Debug; +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; + +pub use bevy_ecs_macros::{ScheduleLabel, SystemSet}; +use bevy_utils::define_old_style_label; +use bevy_utils::label::DynHash; + +use crate::system::{ + ExclusiveSystemParam, ExclusiveSystemParamFunction, IsExclusiveFunctionSystem, + IsFunctionSystem, SystemParam, SystemParamFunction, +}; + +define_old_style_label!(ScheduleLabel); + +pub type BoxedSystemSet = Box; +pub type BoxedScheduleLabel = Box; + +/// Types that identify logical groups of systems. +pub trait SystemSet: DynHash + Debug + Send + Sync + 'static { + /// Returns `true` if this system set is a [`SystemTypeSet`]. + fn is_system_type(&self) -> bool { + false + } + + #[doc(hidden)] + fn dyn_clone(&self) -> Box; +} + +impl PartialEq for dyn SystemSet { + fn eq(&self, other: &Self) -> bool { + self.dyn_eq(other.as_dyn_eq()) + } +} + +impl Eq for dyn SystemSet {} + +impl Hash for dyn SystemSet { + fn hash(&self, state: &mut H) { + self.dyn_hash(state); + } +} + +impl Clone for Box { + fn clone(&self) -> Self { + self.dyn_clone() + } +} + +/// A [`SystemSet`] grouping instances of the same function. +/// +/// This kind of set is automatically populated and thus has some special rules: +/// - You cannot manually add members. +/// - You cannot configure them. +/// - You cannot order something relative to one if it has more than one member. +pub struct SystemTypeSet(PhantomData T>); + +impl SystemTypeSet { + pub(crate) fn new() -> Self { + Self(PhantomData) + } +} + +impl Debug for SystemTypeSet { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("SystemTypeSet") + .field(&std::any::type_name::()) + .finish() + } +} + +impl Hash for SystemTypeSet { + fn hash(&self, _state: &mut H) { + // all systems of a given type are the same + } +} +impl Clone for SystemTypeSet { + fn clone(&self) -> Self { + Self(PhantomData) + } +} + +impl Copy for SystemTypeSet {} + +impl PartialEq for SystemTypeSet { + #[inline] + fn eq(&self, _other: &Self) -> bool { + // all systems of a given type are the same + true + } +} + +impl Eq for SystemTypeSet {} + +impl SystemSet for SystemTypeSet { + fn is_system_type(&self) -> bool { + true + } + + fn dyn_clone(&self) -> Box { + Box::new(*self) + } +} + +/// Types that can be converted into a [`SystemSet`]. +pub trait IntoSystemSet: Sized { + type Set: SystemSet; + + fn into_system_set(self) -> Self::Set; +} + +// systems sets +impl IntoSystemSet<()> for S { + type Set = Self; + + #[inline] + fn into_system_set(self) -> Self::Set { + self + } +} + +// systems +impl IntoSystemSet<(IsFunctionSystem, In, Out, Param, Marker)> for F +where + Param: SystemParam, + F: SystemParamFunction, +{ + type Set = SystemTypeSet; + + #[inline] + fn into_system_set(self) -> Self::Set { + SystemTypeSet::new() + } +} + +// exclusive systems +impl IntoSystemSet<(IsExclusiveFunctionSystem, Param, Marker)> for F +where + Param: ExclusiveSystemParam, + F: ExclusiveSystemParamFunction, +{ + type Set = SystemTypeSet; + + #[inline] + fn into_system_set(self) -> Self::Set { + SystemTypeSet::new() + } +} diff --git a/crates/bevy_ecs/src/schedule_v3/state.rs b/crates/bevy_ecs/src/schedule_v3/state.rs new file mode 100644 index 0000000000000..6baa2383a8e4c --- /dev/null +++ b/crates/bevy_ecs/src/schedule_v3/state.rs @@ -0,0 +1,51 @@ +use std::fmt::Debug; +use std::hash::Hash; +use std::mem; +use std::ops::{Deref, DerefMut}; + +use crate as bevy_ecs; +use crate::change_detection::Mut; +use crate::schedule_v3::{ScheduleLabel, SystemSet, WorldExt}; +use crate::system::{Res, Resource}; +use crate::world::World; + +/// Types that can define states in a finite-state machine. +pub trait Statelike: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug {} +impl Statelike for T where T: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug {} + +/// TBD +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct OnEnter(pub S); + +/// TBD +#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] +pub struct OnExit(pub S); + +/// TBD +#[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)`]). +/// +/// A state transition can be queued through the [`Transition`] 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`]. +#[derive(Resource)] +pub struct Transition(pub Option); + +/// If a state transition is queued in [`Transition`], updates [`State`], then +/// runs the [`OnExit(exited_state)`] and [`OnEnter(entered_state)`] schedules. +pub fn apply_state_transition(world: &mut World) { + world.resource_scope(|world, mut state: Mut>| { + if world.resource::>().0.is_some() { + let entered_state = world.resource_mut::>().0.take().unwrap(); + let exited_state = mem::replace(&mut state.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 cda67f67ce53e..c4146dcb0362e 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -149,9 +149,15 @@ where self.system_meta.name.as_ref(), ); } + fn default_labels(&self) -> Vec { vec![self.func.as_system_label().as_label()] } + + fn default_system_sets(&self) -> Vec> { + let set = crate::schedule_v3::SystemTypeSet::::new(); + vec![Box::new(set)] + } } impl AsSystemLabel<(In, Out, Param, Marker, IsExclusiveFunctionSystem)> diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 7ec7c24489380..6e0b5661457ef 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -453,9 +453,15 @@ where self.system_meta.name.as_ref(), ); } + fn default_labels(&self) -> Vec { vec![self.func.as_system_label().as_label()] } + + fn default_system_sets(&self) -> Vec> { + let set = crate::schedule_v3::SystemTypeSet::::new(); + vec![Box::new(set)] + } } /// A [`SystemLabel`] that was automatically generated for a system on the basis of its `TypeId`. diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 43fcdb41dd3cb..7a2063a2b3df8 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -64,6 +64,10 @@ pub trait System: Send + Sync + 'static { fn default_labels(&self) -> Vec { Vec::new() } + /// Returns the system's default [system sets](crate::schedule_v3::SystemSet). + fn default_system_sets(&self) -> Vec> { + Vec::new() + } /// Gets the system's last change tick fn get_last_change_tick(&self) -> u32; /// Sets 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 0af8caa5eed6e..cfc209819dbeb 100644 --- a/crates/bevy_ecs/src/system/system_piping.rs +++ b/crates/bevy_ecs/src/system/system_piping.rs @@ -141,6 +141,18 @@ impl> System for PipeSystem< self.system_a.set_last_change_tick(last_change_tick); 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()); + system_sets + } } /// An extension trait providing the [`IntoPipeSystem::pipe`] method to pass input from one system into the next. diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 2243af65deb6e..71036fbbefa7c 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -2,7 +2,7 @@ mod entity_ref; mod spawn_batch; mod world_cell; -pub use crate::change_detection::{Mut, Ref}; +pub use crate::change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD}; pub use entity_ref::{EntityMut, EntityRef}; pub use spawn_batch::*; pub use world_cell::*; @@ -64,6 +64,8 @@ pub struct World { pub(crate) archetype_component_access: ArchetypeComponentAccess, pub(crate) change_tick: AtomicU32, pub(crate) last_change_tick: u32, + pub(crate) last_check_tick: u32, + main_thread_validator: MainThreadValidator, } impl Default for World { @@ -81,6 +83,7 @@ impl Default for World { // are detected on first system runs and for direct world queries. change_tick: AtomicU32::new(1), last_change_tick: 0, + last_check_tick: 0, } } } @@ -1583,9 +1586,14 @@ impl World { self.last_change_tick } + /// Iterates all component change ticks and clamps any older than [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE). + /// This prevents overflow and thus prevents false positives. + /// + /// **Note:** Does nothing if the [`World`] counter has not been incremented at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD) + /// times since the previous pass. + // TODO: perf pub fn check_change_ticks(&mut self) { - // Iterate over all component change ticks, clamping their age to max age - // PERF: parallelize + let last_check_tick = self.last_check_tick; let change_tick = self.change_tick(); let Storages { ref mut tables, @@ -1593,10 +1601,21 @@ impl World { ref mut resources, ref mut non_send_resources, } = self.storages; - tables.check_change_ticks(change_tick); - sparse_sets.check_change_ticks(change_tick); - resources.check_change_ticks(change_tick); - non_send_resources.check_change_ticks(change_tick); + + if change_tick.wrapping_sub(last_check_tick) >= CHECK_TICK_THRESHOLD { + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!("check component ticks").entered(); + tables.check_change_ticks(change_tick); + sparse_sets.check_change_ticks(change_tick); + resources.check_change_ticks(change_tick); + non_send_resources.check_change_ticks(change_tick); + + if let Some(mut schedules) = self.get_resource_mut::() { + schedules.check_change_ticks(change_tick, last_check_tick); + } + + self.last_check_tick = change_tick; + } } pub fn clear_entities(&mut self) { diff --git a/crates/bevy_macro_utils/src/lib.rs b/crates/bevy_macro_utils/src/lib.rs index 29f15461221c5..0f2ff8a659707 100644 --- a/crates/bevy_macro_utils/src/lib.rs +++ b/crates/bevy_macro_utils/src/lib.rs @@ -117,6 +117,70 @@ impl BevyManifest { } } +/// Derive a label trait +/// +/// # Args +/// +/// - `input`: The [`syn::DeriveInput`] for struct that is deriving the label trait +/// - `trait_path`: The path [`syn::Path`] to the label trait +pub fn derive_old_style_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStream { + let ident = input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause { + where_token: Default::default(), + predicates: Default::default(), + }); + where_clause.predicates.push( + syn::parse2(quote! { + Self: 'static + Send + Sync + Clone + Eq + ::std::fmt::Debug + ::std::hash::Hash + }) + .unwrap(), + ); + + (quote! { + impl #impl_generics #trait_path for #ident #ty_generics #where_clause { + fn dyn_clone(&self) -> std::boxed::Box { + std::boxed::Box::new(std::clone::Clone::clone(self)) + } + } + }) + .into() +} + +/// Derive a label trait +/// +/// # Args +/// +/// - `input`: The [`syn::DeriveInput`] for struct that is deriving the label trait +/// - `trait_path`: The path [`syn::Path`] to the label trait +pub fn derive_set(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStream { + let ident = input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause { + where_token: Default::default(), + predicates: Default::default(), + }); + where_clause.predicates.push( + syn::parse2(quote! { + Self: 'static + Send + Sync + Clone + Eq + ::std::fmt::Debug + ::std::hash::Hash + }) + .unwrap(), + ); + + (quote! { + impl #impl_generics #trait_path for #ident #ty_generics #where_clause { + fn is_system_type(&self) -> bool { + false + } + + fn dyn_clone(&self) -> std::boxed::Box { + std::boxed::Box::new(std::clone::Clone::clone(self)) + } + } + }) + .into() +} + /// Derive a label trait /// /// # Args diff --git a/crates/bevy_utils/Cargo.toml b/crates/bevy_utils/Cargo.toml index a61ce8d16594d..063d9eb1d550c 100644 --- a/crates/bevy_utils/Cargo.toml +++ b/crates/bevy_utils/Cargo.toml @@ -14,6 +14,7 @@ tracing = { version = "0.1", default-features = false, features = ["std"] } instant = { version = "0.1", features = ["wasm-bindgen"] } uuid = { version = "1.1", features = ["v4", "serde"] } hashbrown = { version = "0.12", features = ["serde"] } +thiserror = "1.0" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = {version = "0.2.0", features = ["js"]} diff --git a/crates/bevy_utils/src/label.rs b/crates/bevy_utils/src/label.rs index 6c504b0283a30..1155574f0f7bc 100644 --- a/crates/bevy_utils/src/label.rs +++ b/crates/bevy_utils/src/label.rs @@ -60,6 +60,47 @@ where } } +/// Macro to define a new label trait +/// +/// # Example +/// +/// ``` +/// # use bevy_utils::define_old_style_label; +/// define_old_style_label!(MyNewLabelTrait); +/// ``` +#[macro_export] +macro_rules! define_old_style_label { + ($label_trait_name:ident) => { + /// A strongly-typed label. + pub trait $label_trait_name: + 'static + Send + Sync + ::std::fmt::Debug + ::bevy_utils::label::DynHash + { + #[doc(hidden)] + fn dyn_clone(&self) -> Box; + } + + impl PartialEq for dyn $label_trait_name { + fn eq(&self, other: &Self) -> bool { + self.dyn_eq(other.as_dyn_eq()) + } + } + + impl Eq for dyn $label_trait_name {} + + impl ::std::hash::Hash for dyn $label_trait_name { + fn hash(&self, state: &mut H) { + self.dyn_hash(state); + } + } + + impl Clone for Box { + fn clone(&self) -> Self { + self.dyn_clone() + } + } + }; +} + /// Macro to define a new label trait /// /// # Example diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index e54065eb5f6bf..82aa329058bc1 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -15,6 +15,7 @@ pub mod label; mod short_names; pub use short_names::get_short_name; pub mod synccell; +pub mod syncunsafecell; mod default; mod float_ord; @@ -24,6 +25,7 @@ pub use default::default; pub use float_ord::*; pub use hashbrown; pub use instant::{Duration, Instant}; +pub use thiserror; pub use tracing; pub use uuid::Uuid; diff --git a/crates/bevy_utils/src/syncunsafecell.rs b/crates/bevy_utils/src/syncunsafecell.rs new file mode 100644 index 0000000000000..1a97bf677c57c --- /dev/null +++ b/crates/bevy_utils/src/syncunsafecell.rs @@ -0,0 +1,94 @@ +pub use core::cell::UnsafeCell; + +/// [`UnsafeCell`], but [`Sync`]. +/// +/// See [tracking issue](https://github.com/rust-lang/rust/issues/95439) for upcoming [`core`] impl, +/// which should replace this one entirely (except `from_mut`). +/// +/// This is just an `UnsafeCell`, except it implements `Sync` +/// if `T` implements `Sync`. +/// +/// `UnsafeCell` doesn't implement `Sync`, to prevent accidental mis-use. +/// You can use `SyncUnsafeCell` instead of `UnsafeCell` to allow it to be +/// shared between threads, if that's intentional. +/// Providing proper synchronization is still the task of the user, +/// making this type just as unsafe to use. +/// +/// See [`UnsafeCell`] for details. +#[repr(transparent)] +pub struct SyncUnsafeCell { + value: UnsafeCell, +} + +// SAFETY: `T` is Sync, caller is responsible for upholding rust safety rules +unsafe impl Sync for SyncUnsafeCell {} + +impl SyncUnsafeCell { + /// Constructs a new instance of `SyncUnsafeCell` which will wrap the specified value. + #[inline] + pub const fn new(value: T) -> Self { + Self { + value: UnsafeCell::new(value), + } + } + + /// Unwraps the value. + #[inline] + pub fn into_inner(self) -> T { + self.value.into_inner() + } +} + +impl SyncUnsafeCell { + /// Gets a mutable pointer to the wrapped value. + /// + /// This can be cast to a pointer of any kind. + /// Ensure that the access is unique (no active references, mutable or not) + /// when casting to `&mut T`, and ensure that there are no mutations + /// or mutable aliases going on when casting to `&T` + #[inline] + pub const fn get(&self) -> *mut T { + self.value.get() + } + + /// Returns a mutable reference to the underlying data. + /// + /// This call borrows the `SyncUnsafeCell` mutably (at compile-time) which + /// guarantees that we possess the only reference. + #[inline] + pub fn get_mut(&mut self) -> &mut T { + self.value.get_mut() + } + + /// Gets a mutable pointer to the wrapped value. + /// + /// See [`UnsafeCell::get`] for details. + #[inline] + pub const fn raw_get(this: *const Self) -> *mut T { + // We can just cast the pointer from `SyncUnsafeCell` to `T` because + // of #[repr(transparent)] on both SyncUnsafeCell and UnsafeCell. + // See UnsafeCell::raw_get. + this as *const T as *mut T + } + + #[inline] + /// Returns a `&SyncUnsafeCell` from a `&mut T`. + pub fn from_mut(t: &mut T) -> &SyncUnsafeCell { + // SAFETY: `&mut` ensures unique access. + unsafe { &*(t as *mut T as *const SyncUnsafeCell) } + } +} + +impl Default for SyncUnsafeCell { + /// Creates an `SyncUnsafeCell`, with the `Default` value for T. + fn default() -> SyncUnsafeCell { + SyncUnsafeCell::new(Default::default()) + } +} + +impl From for SyncUnsafeCell { + /// Creates a new `SyncUnsafeCell` containing the given value. + fn from(t: T) -> SyncUnsafeCell { + SyncUnsafeCell::new(t) + } +} From 0e8c2ce8b20782620828b50a3be561bef5d1b56e Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Tue, 15 Nov 2022 13:01:01 -0800 Subject: [PATCH 02/64] Update schedule.rs --- crates/bevy_ecs/src/schedule_v3/schedule.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index e947bde189982..e46e2cf588c3d 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -11,7 +11,6 @@ use bevy_utils::{ }; use fixedbitset::FixedBitSet; -use petgraph::graph::Node; use petgraph::{algo::tarjan_scc, prelude::*}; use crate::component::ComponentId; From 7451685012a17e2829ba56c2eae3584697771c74 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Tue, 15 Nov 2022 22:35:51 -0800 Subject: [PATCH 03/64] ambiguous_with system type sets --- crates/bevy_ecs/src/schedule_v3/config.rs | 20 +++++++++--------- .../bevy_ecs/src/schedule_v3/graph/utils.rs | 10 ++++----- crates/bevy_ecs/src/schedule_v3/mod.rs | 13 ++++++++++-- crates/bevy_ecs/src/schedule_v3/schedule.rs | 21 ++++++++++--------- 4 files changed, 37 insertions(+), 27 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/config.rs b/crates/bevy_ecs/src/schedule_v3/config.rs index 633cea63d0eae..7baec163d498e 100644 --- a/crates/bevy_ecs/src/schedule_v3/config.rs +++ b/crates/bevy_ecs/src/schedule_v3/config.rs @@ -81,7 +81,7 @@ pub trait IntoSystemSetConfig: sealed::IntoSystemSetConfig { /// Run only if the [`Condition`] is `true` at the time of execution. fn run_if

(self, condition: impl Condition

) -> SystemSetConfig; /// Suppress warnings and errors that would result from "ambiguities" with members of `set`. - fn ambiguous_with(self, set: impl SystemSet) -> SystemSetConfig; + fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemSetConfig; /// Suppress warnings and errors that would result from any "ambiguities". fn ambiguous_with_all(self) -> SystemSetConfig; } @@ -110,7 +110,7 @@ where new_set(self.dyn_clone()).run_if(condition) } - fn ambiguous_with(self, set: impl SystemSet) -> SystemSetConfig { + fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemSetConfig { new_set(self.dyn_clone()).ambiguous_with(set) } @@ -140,7 +140,7 @@ impl IntoSystemSetConfig for BoxedSystemSet { new_set(self).run_if(condition) } - fn ambiguous_with(self, set: impl SystemSet) -> SystemSetConfig { + fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemSetConfig { new_set(self).ambiguous_with(set) } @@ -180,8 +180,8 @@ impl IntoSystemSetConfig for SystemSetConfig { self } - fn ambiguous_with(mut self, set: impl SystemSet) -> Self { - assert!(!set.is_system_type(), "invalid use of system type set"); + fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { + let set = set.into_system_set(); match &mut self.graph_info.ambiguous_with { detection @ Ambiguity::Check => { let mut ambiguous_with = HashSet::new(); @@ -220,7 +220,7 @@ pub trait IntoSystemConfig: sealed::IntoSystemConfig { /// Only run if the [`Condition`] is `true` at the time of execution. fn run_if

(self, condition: impl Condition

) -> SystemConfig; /// Suppress warnings and errors that would result from "ambiguities" with members of `set`. - fn ambiguous_with(self, set: impl SystemSet) -> SystemConfig; + fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemConfig; /// Suppress warnings and errors that would result from any "ambiguities". fn ambiguous_with_all(self) -> SystemConfig; } @@ -249,7 +249,7 @@ where new_system(Box::new(IntoSystem::into_system(self))).run_if(condition) } - fn ambiguous_with(self, set: impl SystemSet) -> SystemConfig { + fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemConfig { new_system(Box::new(IntoSystem::into_system(self))).ambiguous_with(set) } @@ -279,7 +279,7 @@ impl IntoSystemConfig<()> for BoxedSystem<(), ()> { new_system(self).run_if(condition) } - fn ambiguous_with(self, set: impl SystemSet) -> SystemConfig { + fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemConfig { new_system(self).ambiguous_with(set) } @@ -319,8 +319,8 @@ impl IntoSystemConfig<()> for SystemConfig { self } - fn ambiguous_with(mut self, set: impl SystemSet) -> SystemConfig { - assert!(!set.is_system_type(), "invalid use of system type set"); + fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { + let set = set.into_system_set(); match &mut self.graph_info.ambiguous_with { detection @ Ambiguity::Check => { let mut ambiguous_with = HashSet::new(); diff --git a/crates/bevy_ecs/src/schedule_v3/graph/utils.rs b/crates/bevy_ecs/src/schedule_v3/graph/utils.rs index 77469c0d04ae4..35d64e95fd4fc 100644 --- a/crates/bevy_ecs/src/schedule_v3/graph/utils.rs +++ b/crates/bevy_ecs/src/schedule_v3/graph/utils.rs @@ -73,7 +73,7 @@ pub(crate) struct CheckGraphResults { // 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) not_connected: HashSet<(V, V)>, + pub(crate) disconnected: HashSet<(V, V)>, // Edges that are redundant because a longer path exists. pub(crate) transitive_edges: Vec<(V, V)>, // Boolean reachability matrix for the graph. @@ -88,7 +88,7 @@ impl Default for CheckGraphResults { fn default() -> Self { Self { connected: HashSet::new(), - not_connected: HashSet::new(), + disconnected: HashSet::new(), transitive_edges: Vec::new(), reachable: FixedBitSet::new(), tred: DiGraphMap::new(), @@ -125,7 +125,7 @@ where let mut tcls = DiGraphMap::::new(); let mut connected = HashSet::new(); - let mut not_connected = HashSet::new(); + let mut disconnected = HashSet::new(); let mut transitive_edges = Vec::new(); let mut visited = FixedBitSet::with_capacity(n); @@ -176,7 +176,7 @@ where let (a, b) = row_col(index, n); let pair = (topological_order[a], topological_order[b]); if !reachable[index] { - not_connected.insert(pair); + disconnected.insert(pair); } else { connected.insert(pair); } @@ -190,7 +190,7 @@ where CheckGraphResults { connected, - not_connected, + disconnected, transitive_edges, reachable, tred, diff --git a/crates/bevy_ecs/src/schedule_v3/mod.rs b/crates/bevy_ecs/src/schedule_v3/mod.rs index dd2ca9a7f0613..e1e87a411b569 100644 --- a/crates/bevy_ecs/src/schedule_v3/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/mod.rs @@ -78,7 +78,6 @@ mod tests { #[test] #[should_panic] fn dependency_loop() { - // TODO: error on cross dependencies to avoid loops after flattening let mut world = World::new(); let mut schedule = Schedule::new(); @@ -143,7 +142,7 @@ mod tests { // There's only one `foo`, so it's fine. let result = schedule.initialize(&mut world); - assert!(matches!(result, Ok(()))); + assert!(result.is_ok()); // Schedule another `foo`. schedule.add_system(foo); @@ -152,6 +151,16 @@ mod tests { // `foo` are no longer allowed. Too much ambiguity. let result = schedule.initialize(&mut world); assert!(matches!(result, Err(BuildError::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(BuildError::SystemTypeSetAmbiguity(_)))); } #[test] diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index e46e2cf588c3d..1174310fe93ba 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -535,6 +535,7 @@ impl ScheduleMeta { 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)) @@ -573,22 +574,22 @@ impl ScheduleMeta { } } - // system type sets with multiple members cannot be used in dependencies + // system type sets with multiple members cannot be related to for (&set, systems) in systems.iter() { if self.system_sets[&set].is_system_type() { - let mut dep_count = 0; - dep_count += self + let ambiguities = self.ambiguous_with.edges(set).count(); + let mut dependencies = 0; + dependencies += self .dependency .graph .edges_directed(set, Direction::Incoming) .count(); - dep_count += self + dependencies += self .dependency .graph .edges_directed(set, Direction::Outgoing) .count(); - - if systems.len() > 1 && dep_count > 0 { + if systems.len() > 1 && (ambiguities > 0 || dependencies > 0) { let type_set = self.system_sets[&set].0.dyn_clone(); return Err(BuildError::SystemTypeSetAmbiguity(type_set)); } @@ -602,7 +603,6 @@ impl ScheduleMeta { dependency_flattened.add_node(id); } } - for (lhs, rhs, _) in self.dependency.graph.all_edges() { match (lhs, rhs) { (NodeId::System(_), NodeId::System(_)) => { @@ -644,7 +644,7 @@ impl ScheduleMeta { &self.dependency_flattened.topsort, ); - // flatten ambiguities + // flatten allowed ambiguities let mut ambiguous_with_flattened = UnGraphMap::new(); for (lhs, rhs, _) in self.ambiguous_with.all_edges() { match (lhs, rhs) { @@ -673,8 +673,9 @@ impl ScheduleMeta { self.ambiguous_with_flattened = ambiguous_with_flattened; + // check for conflicts let mut conflicting_systems = Vec::new(); - for &(a, b) in flat_results.not_connected.iter() { + 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) @@ -997,7 +998,7 @@ pub enum BuildError { DependencyCycle, #[error("`{0:?}` and `{1:?}` can have a hierarchical OR dependent relationship, not both")] CrossDependency(String, String), - #[error("ambiguous dependency on `{0:?}`, multiple instances of this system exist")] + #[error("ambiguous relationship with `{0:?}`, multiple instances of this system exist")] SystemTypeSetAmbiguity(BoxedSystemSet), #[error("systems with conflicting access have indeterminate execution order")] Ambiguity, From c1f35d2d6c3ca90b722caff0eddfe51747ae447a Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sat, 19 Nov 2022 12:40:33 -0800 Subject: [PATCH 04/64] docs --- crates/bevy_ecs/src/schedule_v3/condition.rs | 11 +++- crates/bevy_ecs/src/schedule_v3/config.rs | 60 ++++++++++++-------- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/condition.rs b/crates/bevy_ecs/src/schedule_v3/condition.rs index f1e0c619f33aa..70f932dd13f67 100644 --- a/crates/bevy_ecs/src/schedule_v3/condition.rs +++ b/crates/bevy_ecs/src/schedule_v3/condition.rs @@ -4,8 +4,10 @@ use crate::system::BoxedSystem; pub type BoxedCondition = BoxedSystem<(), bool>; -/// Functions and closures that convert into [`System`](crate::system::System) -/// trait objects and have [read-only](crate::system::ReadOnlySystemParamFetch) parameters. +/// 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::ReadOnlySystemParamFetch) parameters. pub trait Condition: sealed::Condition {} impl Condition for F where F: sealed::Condition {} @@ -28,6 +30,7 @@ mod sealed { } pub mod helper { + use crate::event::{Event, EventReader}; use crate::schedule_v3::{State, Statelike}; use crate::system::{Res, Resource}; @@ -55,6 +58,8 @@ pub mod helper { /// Generates a [`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, @@ -83,6 +88,8 @@ pub mod helper { /// Generates a [`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 { diff --git a/crates/bevy_ecs/src/schedule_v3/config.rs b/crates/bevy_ecs/src/schedule_v3/config.rs index 7baec163d498e..da6b0de744ffd 100644 --- a/crates/bevy_ecs/src/schedule_v3/config.rs +++ b/crates/bevy_ecs/src/schedule_v3/config.rs @@ -37,7 +37,9 @@ pub(super) fn new_set_unchecked(set: BoxedSystemSet) -> SystemSetConfig { } fn new_set(set: BoxedSystemSet) -> SystemSetConfig { - assert!(!set.is_system_type()); + // system type sets are automatically populated + // to avoid unintentionally broad changes, they cannot be configured + assert!(!set.is_system_type(), "invalid use of system type set"); new_set_unchecked(set) } @@ -59,7 +61,7 @@ fn new_condition

(condition: impl Condition

) -> BoxedCondition { let condition_system = IntoSystem::into_system(condition); assert!( condition_system.is_send(), - "condition accesses thread-local resources (currently not supported)" + "Condition accesses thread-local resources. This is not currently supported." ); Box::new(condition_system) @@ -72,17 +74,22 @@ pub trait IntoSystemSetConfig: sealed::IntoSystemSetConfig { /// Convert into a [`SystemSetConfig`]. #[doc(hidden)] fn into_config(self) -> SystemSetConfig; - /// Add to `set` membership. + /// Add to the provided `set`. fn in_set(self, set: impl SystemSet) -> SystemSetConfig; - /// Run before all members of `set`. + /// Run before all systems in `set`. fn before(self, set: impl IntoSystemSet) -> SystemSetConfig; - /// Run after all members of `set`. + /// Run after all systems in `set`. fn after(self, set: impl IntoSystemSet) -> SystemSetConfig; - /// Run only if the [`Condition`] is `true` at the time of execution. + /// 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 "ambiguities" with members of `set`. + /// 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 any "ambiguities". + /// 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; } @@ -213,15 +220,20 @@ pub trait IntoSystemConfig: sealed::IntoSystemConfig { fn into_config(self) -> SystemConfig; /// Add to `set` membership. fn in_set(self, set: impl SystemSet) -> SystemConfig; - /// Run before all members of `set`. + /// Run before all systems in `set`. fn before(self, set: impl IntoSystemSet) -> SystemConfig; - /// Run after all members of `set`. + /// Run after all systems in `set`. fn after(self, set: impl IntoSystemSet) -> SystemConfig; - /// Only run if the [`Condition`] is `true` at the time of execution. + /// 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 "ambiguities" with members of `set`. + /// 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 any "ambiguities". + /// 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; } @@ -371,6 +383,7 @@ mod sealed { /// 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, } @@ -383,24 +396,24 @@ where #[doc(hidden)] fn into_configs(self) -> SystemConfigs; - /// Add to `set` membership. + /// Add these systems to the provided `set`. fn in_set(self, set: impl SystemSet) -> SystemConfigs { self.into_configs().in_set(set) } - /// Run before all members of `set`. + /// Run before all systems in `set`. fn before(self, set: impl IntoSystemSet) -> SystemConfigs { self.into_configs().before(set) } - /// Run after all members of `set`. + /// Run after all systems in `set`. fn after(self, set: impl IntoSystemSet) -> SystemConfigs { self.into_configs().after(set) } - /// Treat this collection as a sequence. + /// Treat this collection as a sequence of systems. /// - /// Ordering constraints will be applied between the successive collection elements. + /// Ordering constraints will be applied between the successive elements. fn chain(self) -> SystemConfigs { self.into_configs().chain() } @@ -453,6 +466,7 @@ impl IntoSystemConfigs<()> for SystemConfigs { /// 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, } @@ -465,24 +479,24 @@ where #[doc(hidden)] fn into_configs(self) -> SystemSetConfigs; - /// Add to `set` membership. + /// 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 members of `set`. + /// Run before all systems in `set`. fn before(self, set: impl IntoSystemSet) -> SystemSetConfigs { self.into_configs().before(set) } - /// Run after all members of `set`. + /// Run after all systems in `set`. fn after(self, set: impl IntoSystemSet) -> SystemSetConfigs { self.into_configs().after(set) } - /// Treat this collection as a sequence. + /// Treat this collection as a sequence of system sets. /// - /// Ordering constraints will be applied between the successive collection elements. + /// Ordering constraints will be applied between the successive elements. fn chain(self) -> SystemSetConfigs { self.into_configs().chain() } From 33f7c98ab5f84cd91f6e4ec7e96167b96473eb56 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sat, 19 Nov 2022 12:47:54 -0800 Subject: [PATCH 05/64] move `petgraph` to utils --- crates/bevy_ecs/Cargo.toml | 2 -- crates/bevy_ecs/src/schedule_v3/graph/utils.rs | 6 ++++-- crates/bevy_ecs/src/schedule_v3/schedule.rs | 2 +- crates/bevy_utils/Cargo.toml | 1 + crates/bevy_utils/src/lib.rs | 1 + 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 03f368d0f7258..000c2121a7d1c 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -28,8 +28,6 @@ fxhash = "0.2" downcast-rs = "1.2" serde = { version = "1", features = ["derive"] } -petgraph = "0.6" - [dev-dependencies] rand = "0.8" diff --git a/crates/bevy_ecs/src/schedule_v3/graph/utils.rs b/crates/bevy_ecs/src/schedule_v3/graph/utils.rs index 35d64e95fd4fc..aa3f0f84a7e3b 100644 --- a/crates/bevy_ecs/src/schedule_v3/graph/utils.rs +++ b/crates/bevy_ecs/src/schedule_v3/graph/utils.rs @@ -1,8 +1,10 @@ use crate::schedule_v3::set::*; -use bevy_utils::{HashMap, HashSet}; +use bevy_utils::{ + petgraph::{graphmap::NodeTrait, prelude::*}, + HashMap, HashSet, +}; use fixedbitset::FixedBitSet; -use petgraph::{graphmap::NodeTrait, prelude::*}; use std::fmt::Debug; diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index 1174310fe93ba..3e51db1428264 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -5,13 +5,13 @@ use std::{ }; use bevy_utils::{ + petgraph::{algo::tarjan_scc, prelude::*}, thiserror::Error, tracing::{error, warn}, HashMap, HashSet, }; use fixedbitset::FixedBitSet; -use petgraph::{algo::tarjan_scc, prelude::*}; use crate::component::ComponentId; use crate::{ diff --git a/crates/bevy_utils/Cargo.toml b/crates/bevy_utils/Cargo.toml index 063d9eb1d550c..5a019dbc38455 100644 --- a/crates/bevy_utils/Cargo.toml +++ b/crates/bevy_utils/Cargo.toml @@ -14,6 +14,7 @@ tracing = { version = "0.1", default-features = false, features = ["std"] } instant = { version = "0.1", features = ["wasm-bindgen"] } uuid = { version = "1.1", features = ["v4", "serde"] } hashbrown = { version = "0.12", features = ["serde"] } +petgraph = "0.6" thiserror = "1.0" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 82aa329058bc1..a6a4aebcb254c 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -25,6 +25,7 @@ pub use default::default; pub use float_ord::*; pub use hashbrown; pub use instant::{Duration, Instant}; +pub use petgraph; pub use thiserror; pub use tracing; pub use uuid::Uuid; From d1ed54115b5780c818f54d14973ba36445b7b64e Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sat, 19 Nov 2022 12:55:18 -0800 Subject: [PATCH 06/64] clean up warnings --- crates/bevy_ecs/src/schedule_v3/condition.rs | 1 - crates/bevy_ecs/src/schedule_v3/graph/mod.rs | 2 +- crates/bevy_ecs/src/schedule_v3/graph/utils.rs | 8 ++------ crates/bevy_ecs/src/schedule_v3/mod.rs | 10 +++------- crates/bevy_ecs/src/schedule_v3/schedule.rs | 12 +++--------- crates/bevy_ecs/src/schedule_v3/set.rs | 1 - crates/bevy_ecs/src/schedule_v3/state.rs | 3 +-- 7 files changed, 10 insertions(+), 27 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/condition.rs b/crates/bevy_ecs/src/schedule_v3/condition.rs index 70f932dd13f67..cfcd02439545c 100644 --- a/crates/bevy_ecs/src/schedule_v3/condition.rs +++ b/crates/bevy_ecs/src/schedule_v3/condition.rs @@ -30,7 +30,6 @@ mod sealed { } pub mod helper { - use crate::event::{Event, EventReader}; use crate::schedule_v3::{State, Statelike}; use crate::system::{Res, Resource}; diff --git a/crates/bevy_ecs/src/schedule_v3/graph/mod.rs b/crates/bevy_ecs/src/schedule_v3/graph/mod.rs index 280f9fc05b67f..753ec9d9494e3 100644 --- a/crates/bevy_ecs/src/schedule_v3/graph/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/graph/mod.rs @@ -1,2 +1,2 @@ mod utils; -pub use utils::*; +pub(super) use utils::*; diff --git a/crates/bevy_ecs/src/schedule_v3/graph/utils.rs b/crates/bevy_ecs/src/schedule_v3/graph/utils.rs index aa3f0f84a7e3b..327c0fb6892f5 100644 --- a/crates/bevy_ecs/src/schedule_v3/graph/utils.rs +++ b/crates/bevy_ecs/src/schedule_v3/graph/utils.rs @@ -54,12 +54,6 @@ pub(crate) struct GraphInfo { pub(crate) ambiguous_with: Ambiguity, } -#[derive(Clone)] -pub(crate) struct IndexedGraphInfo { - pub(crate) sets: HashSet, - pub(crate) edges: HashSet<(DependencyEdgeKind, NodeId)>, -} - /// 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); @@ -81,8 +75,10 @@ pub(crate) struct CheckGraphResults { // Boolean reachability matrix for the graph. pub(crate) reachable: FixedBitSet, // Variant of the graph with the fewest possible edges. + #[allow(dead_code)] pub(crate) tred: DiGraphMap, // Variant of the graph with the most possible edges. + #[allow(dead_code)] pub(crate) tcls: DiGraphMap, } diff --git a/crates/bevy_ecs/src/schedule_v3/mod.rs b/crates/bevy_ecs/src/schedule_v3/mod.rs index e1e87a411b569..7377e7d0fea2a 100644 --- a/crates/bevy_ecs/src/schedule_v3/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/mod.rs @@ -1,4 +1,3 @@ -#![allow(warnings)] mod condition; mod config; mod executor; @@ -24,6 +23,7 @@ mod tests { use crate::system::*; use crate::world::World; + #[allow(dead_code)] #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] enum TestSchedule { A, @@ -78,9 +78,7 @@ mod tests { #[test] #[should_panic] fn dependency_loop() { - let mut world = World::new(); let mut schedule = Schedule::new(); - schedule.configure_set(TestSet::X.after(TestSet::X)); } @@ -109,9 +107,7 @@ mod tests { #[test] #[should_panic] fn hierarchy_loop() { - let mut world = World::new(); let mut schedule = Schedule::new(); - schedule.configure_set(TestSet::X.in_set(TestSet::X)); } @@ -217,8 +213,8 @@ mod tests { #[derive(Resource)] struct X; - fn res_ref(x: Res) {} - fn res_mut(mut x: ResMut) {} + fn res_ref(_x: Res) {} + fn res_mut(_x: ResMut) {} let mut world = World::new(); let mut schedule = Schedule::new(); diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index 3e51db1428264..e3fcc22a88568 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -25,21 +25,15 @@ use crate::{ #[derive(Default, Resource)] pub struct Schedules { inner: HashMap>, - default: Option, } impl Schedules { pub fn new() -> Self { Self { inner: HashMap::new(), - default: None, } } - pub(crate) fn set_default(&mut self, label: impl ScheduleLabel) { - self.default = Some(label.dyn_clone()); - } - /// Insert a new labeled schedule into the map. /// /// # Errors @@ -180,7 +174,7 @@ impl Schedule { /// /// **Note:** Does nothing if the [`World`] counter has not been incremented at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD) /// times since the previous pass. - pub(crate) fn check_change_ticks(&mut self, change_tick: u32, last_change_tick: u32) { + pub(crate) fn check_change_ticks(&mut self, _change_tick: u32, _last_change_tick: u32) { #[cfg(feature = "trace")] let _span = bevy_utils::tracing::info_span!("check schedule ticks").entered(); todo!(); @@ -346,7 +340,7 @@ impl ScheduleMeta { let SystemSetConfig { set, mut graph_info, - mut conditions, + conditions, } = set.into_config(); let id = match self.system_set_ids.get(&set) { @@ -469,7 +463,7 @@ impl ScheduleMeta { match ambiguous_with { Ambiguity::Check => (), Ambiguity::IgnoreWithSet(ambigous_with) => { - for (other) in ambigous_with + for other in ambigous_with .into_iter() .map(|set| self.system_set_ids[&set]) { diff --git a/crates/bevy_ecs/src/schedule_v3/set.rs b/crates/bevy_ecs/src/schedule_v3/set.rs index 6ccff2745b3ec..d6a7247a77ebb 100644 --- a/crates/bevy_ecs/src/schedule_v3/set.rs +++ b/crates/bevy_ecs/src/schedule_v3/set.rs @@ -1,4 +1,3 @@ -use std::any::TypeId; use std::fmt::Debug; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; diff --git a/crates/bevy_ecs/src/schedule_v3/state.rs b/crates/bevy_ecs/src/schedule_v3/state.rs index 6baa2383a8e4c..782831dffa724 100644 --- a/crates/bevy_ecs/src/schedule_v3/state.rs +++ b/crates/bevy_ecs/src/schedule_v3/state.rs @@ -1,12 +1,11 @@ use std::fmt::Debug; use std::hash::Hash; use std::mem; -use std::ops::{Deref, DerefMut}; use crate as bevy_ecs; use crate::change_detection::Mut; use crate::schedule_v3::{ScheduleLabel, SystemSet, WorldExt}; -use crate::system::{Res, Resource}; +use crate::system::Resource; use crate::world::World; /// Types that can define states in a finite-state machine. From 7f86219c0458d2329a116d0d95ae6879b0fca098 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sat, 19 Nov 2022 13:49:22 -0800 Subject: [PATCH 07/64] document graph alg --- .../bevy_ecs/src/schedule_v3/graph/utils.rs | 87 +++++++++++-------- crates/bevy_ecs/src/schedule_v3/schedule.rs | 4 +- 2 files changed, 53 insertions(+), 38 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/graph/utils.rs b/crates/bevy_ecs/src/schedule_v3/graph/utils.rs index 327c0fb6892f5..917673d2a857b 100644 --- a/crates/bevy_ecs/src/schedule_v3/graph/utils.rs +++ b/crates/bevy_ecs/src/schedule_v3/graph/utils.rs @@ -66,35 +66,46 @@ pub(crate) fn row_col(index: usize, num_cols: usize) -> (usize, usize) { } 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)>, - // Boolean reachability matrix for the graph. - pub(crate) reachable: FixedBitSet, - // Variant of the graph with the fewest possible edges. + // Variant of the graph with no transitive edges. #[allow(dead_code)] - pub(crate) tred: DiGraphMap, - // Variant of the graph with the most possible edges. + pub(crate) transitive_reduction: DiGraphMap, + // Variant of the graph with all possible transitive edges. #[allow(dead_code)] - pub(crate) tcls: DiGraphMap, + 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(), - reachable: FixedBitSet::new(), - tred: DiGraphMap::new(), - tcls: DiGraphMap::new(), + transitive_reduction: DiGraphMap::new(), + transitive_closure: DiGraphMap::new(), } } } +/// Processes a DAG and computes: +/// - transitive reduction (along with the set of transitive 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], @@ -107,60 +118,63 @@ where } 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 tsorted = DiGraphMap::::new(); + let mut topsorted = DiGraphMap::::new(); // iterate nodes in topological order for (i, &node) in topological_order.iter().enumerate() { map.insert(node, i); - tsorted.add_node(node.clone()); + topsorted.add_node(node.clone()); // insert nodes as successors to their predecessors for pred in graph.neighbors_directed(node, Direction::Incoming) { - tsorted.add_edge(pred, node, ()); + topsorted.add_edge(pred, node, ()); } } - let mut tred = DiGraphMap::::new(); - let mut tcls = DiGraphMap::::new(); - + 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); - let mut reachable = FixedBitSet::with_capacity(n * n); // iterate nodes in topological order - for node in tsorted.nodes() { - tred.add_node(node); - tcls.add_node(node); + for node in topsorted.nodes() { + transitive_reduction.add_node(node); + transitive_closure.add_node(node); } // iterate nodes in reverse topological order - for a in tsorted.nodes().rev() { + for a in topsorted.nodes().rev() { let index_a = *map.get(&a).unwrap(); // iterate their successors in topological order - for b in tsorted.neighbors_directed(a, Direction::Outgoing) { + 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 - tred.add_edge(a, b, ()); - tcls.add_edge(a, b, ()); - reachable.set(index(index_a, index_b, n), true); + transitive_reduction.add_edge(a, b, ()); + transitive_closure.add_edge(a, b, ()); + reachable.insert(index(index_a, index_b, n)); - let successors = tcls + let successors = transitive_closure .neighbors_directed(b, Direction::Outgoing) .collect::>(); for c in successors.into_iter() { let index_c = *map.get(&c).unwrap(); debug_assert!(index_b < index_c); if !visited[index_c] { - visited.set(index_c, true); - tcls.add_edge(a, c, ()); - reachable.set(index(index_a, index_c, n), true); + 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)); } } @@ -168,30 +182,31 @@ where visited.clear(); } + // fill reachability matrix and partition nodes into "connected by path" and not for i in 0..(n - 1) { - // reachable is upper triangular because the nodes are in topological order + // 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] { - disconnected.insert(pair); - } else { + if reachable[index] { connected.insert(pair); + } else { + disconnected.insert(pair); } } } - // Fill diagonal. + // fill diagonal (nodes reach themselves) for i in 0..n { reachable.set(index(i, i, n), true); } CheckGraphResults { + reachable, connected, disconnected, transitive_edges, - reachable, - tred, - tcls, + transitive_reduction, + transitive_closure, } } diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index e3fcc22a88568..771d6b1e9f07c 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -13,15 +13,15 @@ use bevy_utils::{ use fixedbitset::FixedBitSet; -use crate::component::ComponentId; use crate::{ self as bevy_ecs, + component::ComponentId, schedule_v3::*, system::{BoxedSystem, Resource}, world::World, }; -/// Stores [`ScheduleLabel`]-[`Schedule`] pairs. +/// Resource for storing [`Schedule`]s. #[derive(Default, Resource)] pub struct Schedules { inner: HashMap>, From 2ee5ef6d0513496f3a08db07c3fd46e61679e360 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sat, 19 Nov 2022 18:12:17 -0800 Subject: [PATCH 08/64] fix condition issue, flatten graph mod, get executor kind --- crates/bevy_ecs/src/schedule_v3/config.rs | 2 +- .../bevy_ecs/src/schedule_v3/executor/mod.rs | 1 + .../schedule_v3/executor/multi_threaded.rs | 6 +- .../src/schedule_v3/executor/simple.rs | 9 ++- .../schedule_v3/executor/single_threaded.rs | 8 ++- crates/bevy_ecs/src/schedule_v3/graph/mod.rs | 2 - .../{graph/utils.rs => graph_utils.rs} | 22 +++---- crates/bevy_ecs/src/schedule_v3/mod.rs | 6 +- crates/bevy_ecs/src/schedule_v3/schedule.rs | 60 +++++++++---------- 9 files changed, 66 insertions(+), 50 deletions(-) delete mode 100644 crates/bevy_ecs/src/schedule_v3/graph/mod.rs rename crates/bevy_ecs/src/schedule_v3/{graph/utils.rs => graph_utils.rs} (91%) diff --git a/crates/bevy_ecs/src/schedule_v3/config.rs b/crates/bevy_ecs/src/schedule_v3/config.rs index da6b0de744ffd..4c48dba2c19e7 100644 --- a/crates/bevy_ecs/src/schedule_v3/config.rs +++ b/crates/bevy_ecs/src/schedule_v3/config.rs @@ -4,7 +4,7 @@ use bevy_utils::HashSet; use crate::{ schedule_v3::{ condition::{BoxedCondition, Condition}, - graph::{Ambiguity, DependencyEdgeKind, GraphInfo}, + graph_utils::{Ambiguity, DependencyEdgeKind, GraphInfo}, set::{BoxedSystemSet, IntoSystemSet, SystemSet}, }, system::{BoxedSystem, IntoSystem, System}, diff --git a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs index 1d5830ab0a3d9..3d00d829fb039 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs @@ -19,6 +19,7 @@ use crate::{ /// 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); } diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index 5452c3fa9b610..8ca59f91b362c 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -10,7 +10,7 @@ use fixedbitset::FixedBitSet; use crate::{ archetype::ArchetypeComponentId, query::Access, - schedule_v3::{is_apply_system_buffers, SystemExecutor, SystemSchedule}, + schedule_v3::{is_apply_system_buffers, ExecutorKind, SystemExecutor, SystemSchedule}, world::World, }; @@ -71,6 +71,10 @@ impl Default for MultiThreadedExecutor { } 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(); diff --git a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs index 70415017f2b28..cf33351bf8113 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs @@ -3,7 +3,7 @@ use bevy_utils::tracing::{info_span, Instrument}; use fixedbitset::FixedBitSet; use crate::{ - schedule_v3::{SystemExecutor, SystemSchedule}, + schedule_v3::{ExecutorKind, SystemExecutor, SystemSchedule}, world::World, }; @@ -18,6 +18,10 @@ pub struct SimpleExecutor { } 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(); @@ -121,6 +125,9 @@ impl SystemExecutor for SimpleExecutor { let _apply_buffers_span = info_span!("apply_buffers", name = &*name).entered(); system.apply_buffers(world); } + + self.completed_sets.clear(); + self.completed_systems.clear(); } } diff --git a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs index e768abf9f3e9a..4dd5d570b5269 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs @@ -3,7 +3,7 @@ use bevy_utils::tracing::{info_span, Instrument}; use fixedbitset::FixedBitSet; use crate::{ - schedule_v3::{is_apply_system_buffers, SystemExecutor, SystemSchedule}, + schedule_v3::{is_apply_system_buffers, ExecutorKind, SystemExecutor, SystemSchedule}, world::World, }; @@ -19,6 +19,10 @@ pub struct SingleThreadedExecutor { } 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(); @@ -131,6 +135,8 @@ impl SystemExecutor for SingleThreadedExecutor { } self.apply_system_buffers(schedule, world); + self.completed_sets.clear(); + self.completed_systems.clear(); } } diff --git a/crates/bevy_ecs/src/schedule_v3/graph/mod.rs b/crates/bevy_ecs/src/schedule_v3/graph/mod.rs deleted file mode 100644 index 753ec9d9494e3..0000000000000 --- a/crates/bevy_ecs/src/schedule_v3/graph/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod utils; -pub(super) use utils::*; diff --git a/crates/bevy_ecs/src/schedule_v3/graph/utils.rs b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs similarity index 91% rename from crates/bevy_ecs/src/schedule_v3/graph/utils.rs rename to crates/bevy_ecs/src/schedule_v3/graph_utils.rs index 917673d2a857b..6603e5ec68d33 100644 --- a/crates/bevy_ecs/src/schedule_v3/graph/utils.rs +++ b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs @@ -66,18 +66,18 @@ pub(crate) fn row_col(index: usize, num_cols: usize) -> (usize, usize) { } pub(crate) struct CheckGraphResults { - // Boolean reachability matrix for the graph. + /// Boolean reachability matrix for the graph. pub(crate) reachable: FixedBitSet, - // Pairs of nodes that have a path connecting them. + /// 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. + /// 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. + /// Edges that are redundant because a longer path exists. pub(crate) transitive_edges: Vec<(V, V)>, - // Variant of the graph with no transitive edges. + /// Variant of the graph with no transitive edges. #[allow(dead_code)] pub(crate) transitive_reduction: DiGraphMap, - // Variant of the graph with all possible transitive edges. + /// Variant of the graph with all possible transitive edges. #[allow(dead_code)] pub(crate) transitive_closure: DiGraphMap, } @@ -96,7 +96,7 @@ impl Default for CheckGraphResults { } /// Processes a DAG and computes: -/// - transitive reduction (along with the set of transitive edges) +/// - transitive reduction (along with the set of removed edges) /// - transitive closure /// - reachability matrix (as a bitset) /// - pairs of nodes connected by a path @@ -182,7 +182,7 @@ where visited.clear(); } - // fill reachability matrix and partition nodes into "connected by path" and not + // 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) { @@ -197,9 +197,9 @@ where } // fill diagonal (nodes reach themselves) - for i in 0..n { - reachable.set(index(i, i, n), true); - } + // for i in 0..n { + // reachable.set(index(i, i, n), true); + // } CheckGraphResults { reachable, diff --git a/crates/bevy_ecs/src/schedule_v3/mod.rs b/crates/bevy_ecs/src/schedule_v3/mod.rs index 7377e7d0fea2a..a0b500c919d3e 100644 --- a/crates/bevy_ecs/src/schedule_v3/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/mod.rs @@ -1,7 +1,7 @@ mod condition; mod config; mod executor; -mod graph; +mod graph_utils; mod migration; mod schedule; mod set; @@ -10,7 +10,7 @@ mod state; pub use self::condition::*; pub use self::config::*; pub use self::executor::*; -use self::graph::*; +use self::graph_utils::*; pub use self::migration::*; pub use self::schedule::*; pub use self::set::*; @@ -18,6 +18,8 @@ pub use self::state::*; #[cfg(test)] mod tests { + use bevy_utils::HashSet; + use super::*; use crate as bevy_ecs; use crate::system::*; diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index 771d6b1e9f07c..46085e0e66bf8 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -146,7 +146,11 @@ impl Schedule { self.graph.set_default_set(set); } - pub fn set_executor(&mut self, executor: ExecutorKind) { + pub fn get_executor_kind(&self) -> ExecutorKind { + self.executor.kind() + } + + pub fn set_executor_kind(&mut self, executor: ExecutorKind) { self.executor = match executor { ExecutorKind::Simple => Box::new(SimpleExecutor::new()), ExecutorKind::SingleThreaded => Box::new(SingleThreadedExecutor::new()), @@ -164,6 +168,7 @@ impl Schedule { self.graph.initialize(world); self.graph.update_schedule(&mut self.executable)?; self.executor.init(&self.executable); + self.graph.changed = false; } Ok(()) @@ -373,7 +378,7 @@ impl ScheduleMeta { } fn chain(&mut self, nodes: Vec) { - for pair in nodes.as_slice().windows(2) { + for pair in nodes.windows(2) { self.dependency.graph.add_edge(pair[0], pair[1], ()); } } @@ -396,30 +401,23 @@ impl ScheduleMeta { } fn check_edges(&mut self, id: &NodeId, graph_info: &GraphInfo) -> Result<(), BuildError> { - for (_, other) in graph_info.dependencies.iter() { - match self.system_set_ids.get(other) { - Some(other_id) => { - if id == other_id { - return Err(BuildError::DependencyLoop(other.dyn_clone())); + for (_, set) in graph_info.dependencies.iter() { + match self.system_set_ids.get(set) { + Some(set_id) => { + if id == set_id { + return Err(BuildError::DependencyLoop(set.dyn_clone())); } } None => { - self.add_set(other.dyn_clone()); + self.add_set(set.dyn_clone()); } } } if let Ambiguity::IgnoreWithSet(ambiguous_with) = &graph_info.ambiguous_with { - for other in ambiguous_with.iter() { - match self.system_set_ids.get(other) { - Some(other_id) => { - if id == other_id { - return Err(BuildError::DependencyLoop(other.dyn_clone())); - } - } - None => { - self.add_set(other.dyn_clone()); - } + for set in ambiguous_with.iter() { + if !self.system_set_ids.contains_key(set) { + self.add_set(set.dyn_clone()); } } } @@ -449,13 +447,13 @@ impl ScheduleMeta { self.dependency.graph.add_node(id); } - for (edge_kind, other) in dependencies + for (edge_kind, set) in dependencies .into_iter() .map(|(edge_kind, set)| (edge_kind, self.system_set_ids[&set])) { let (lhs, rhs) = match edge_kind { - DependencyEdgeKind::Before => (id, other), - DependencyEdgeKind::After => (other, id), + DependencyEdgeKind::Before => (id, set), + DependencyEdgeKind::After => (set, id), }; self.dependency.graph.add_edge(lhs, rhs, ()); } @@ -463,11 +461,11 @@ impl ScheduleMeta { match ambiguous_with { Ambiguity::Check => (), Ambiguity::IgnoreWithSet(ambigous_with) => { - for other in ambigous_with + for set in ambigous_with .into_iter() .map(|set| self.system_set_ids[&set]) { - self.ambiguous_with.add_edge(id, other, ()); + self.ambiguous_with.add_edge(id, set, ()); } } Ambiguity::IgnoreAll => { @@ -568,7 +566,7 @@ impl ScheduleMeta { } } - // system type sets with multiple members cannot be related to + // can't depend on or be ambiguous with system type sets that has many instances for (&set, systems) in systems.iter() { if self.system_sets[&set].is_system_type() { let ambiguities = self.ambiguous_with.edges(set).count(); @@ -735,22 +733,22 @@ impl ScheduleMeta { .hierarchy .topsort .iter() - .filter(|&id| id.is_system()) .cloned() .enumerate() + .filter(|&(_i, id)| id.is_system()) .collect::>(); let (hg_set_idxs, hg_set_ids): (Vec<_>, Vec<_>) = self .hierarchy .topsort .iter() - .filter(|&id| { - // ignore system type sets - // ignore system sets that have no conditions - id.is_set() && self.conditions.get(id).filter(|v| !v.is_empty()).is_some() - }) .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.conditions.get(&id).filter(|v| !v.is_empty()).is_some() + }) .unzip(); let set_count = hg_set_ids.len(); @@ -761,7 +759,7 @@ impl ScheduleMeta { let mut sets_of_sets = vec![FixedBitSet::with_capacity(set_count); set_count]; for (i, &row) in hg_set_idxs.iter().enumerate() { let bitset = &mut sets_of_sets[i]; - for (idx, &col) in hg_set_idxs.iter().enumerate() { + for (idx, &col) in hg_set_idxs.iter().enumerate().skip(i) { let is_descendant = result.reachable[index(row, col, node_count)]; bitset.set(idx, is_descendant); } From 0152ce9e546734336f2007c6286125a5e6f0c856 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sat, 19 Nov 2022 21:55:08 -0800 Subject: [PATCH 09/64] clippy being pedantic --- crates/bevy_ecs/src/schedule_v3/config.rs | 12 ++-- .../schedule_v3/executor/multi_threaded.rs | 39 ++++++------ .../src/schedule_v3/executor/simple.rs | 2 +- .../schedule_v3/executor/single_threaded.rs | 2 +- .../bevy_ecs/src/schedule_v3/graph_utils.rs | 4 +- crates/bevy_ecs/src/schedule_v3/mod.rs | 2 - crates/bevy_ecs/src/schedule_v3/schedule.rs | 60 ++++++++++--------- 7 files changed, 61 insertions(+), 60 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/config.rs b/crates/bevy_ecs/src/schedule_v3/config.rs index 4c48dba2c19e7..fac15a7dc452c 100644 --- a/crates/bevy_ecs/src/schedule_v3/config.rs +++ b/crates/bevy_ecs/src/schedule_v3/config.rs @@ -426,7 +426,7 @@ impl IntoSystemConfigs<()> for SystemConfigs { fn in_set(mut self, set: impl SystemSet) -> Self { assert!(!set.is_system_type(), "invalid use of system type set"); - for config in self.systems.iter_mut() { + for config in &mut self.systems { config.graph_info.sets.insert(set.dyn_clone()); } @@ -435,7 +435,7 @@ impl IntoSystemConfigs<()> for SystemConfigs { fn before(mut self, set: impl IntoSystemSet) -> Self { let set = set.into_system_set(); - for config in self.systems.iter_mut() { + for config in &mut self.systems { config .graph_info .dependencies @@ -447,7 +447,7 @@ impl IntoSystemConfigs<()> for SystemConfigs { fn after(mut self, set: impl IntoSystemSet) -> Self { let set = set.into_system_set(); - for config in self.systems.iter_mut() { + for config in &mut self.systems { config .graph_info .dependencies @@ -509,7 +509,7 @@ impl IntoSystemSetConfigs for SystemSetConfigs { fn in_set(mut self, set: impl SystemSet) -> Self { assert!(!set.is_system_type(), "invalid use of system type set"); - for config in self.sets.iter_mut() { + for config in &mut self.sets { config.graph_info.sets.insert(set.dyn_clone()); } @@ -518,7 +518,7 @@ impl IntoSystemSetConfigs for SystemSetConfigs { fn before(mut self, set: impl IntoSystemSet) -> Self { let set = set.into_system_set(); - for config in self.sets.iter_mut() { + for config in &mut self.sets { config .graph_info .dependencies @@ -530,7 +530,7 @@ impl IntoSystemSetConfigs for SystemSetConfigs { fn after(mut self, set: impl IntoSystemSet) -> Self { let set = set.into_system_set(); - for config in self.sets.iter_mut() { + for config in &mut self.sets { config .graph_info .dependencies diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index 8ca59f91b362c..00843f3d8b29e 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -143,7 +143,7 @@ impl SystemExecutor for MultiThreadedExecutor { self.finish_system_and_signal_dependents(index); } - self.rebuild_active_access() + self.rebuild_active_access(); } } @@ -202,36 +202,33 @@ impl MultiThreadedExecutor { // skip systems we've already seen during this call self.seen_ready_systems.insert(system_index); - if self.system_task_metadata[system_index].is_exclusive { + if !self.system_task_metadata[system_index].is_exclusive { + // SAFETY: no exclusive system running + let world = unsafe { &*world.get() }; + if !self.can_run(system_index, schedule, world) { + continue; + } + if !self.should_run(system_index, schedule, world) { + continue; + } + self.spawn_system_task(scope, system_index, schedule, world); + } else { { // SAFETY: no exclusive system running let world = unsafe { &*world.get() }; if !self.can_run(system_index, schedule, world) { // the `break` here emulates single-threaded runner behavior - // without it, the exclusive system will likely be stalled out + // without it, exclusive systems would likely be stalled out break; } if !self.should_run(system_index, schedule, world) { continue; } } - { - // SAFETY: no system running - let world = unsafe { &mut *world.get() }; - self.spawn_exclusive_system_task(scope, system_index, schedule, world); - break; - } - } else { - // SAFETY: no exclusive system running - let world = unsafe { &*world.get() }; - if !self.can_run(system_index, schedule, world) { - continue; - } - if !self.should_run(system_index, schedule, world) { - continue; - } - - self.spawn_system_task(scope, system_index, schedule, world); + // SAFETY: no system running + let world = unsafe { &mut *world.get() }; + self.spawn_exclusive_system_task(scope, system_index, schedule, world); + break; } } @@ -491,7 +488,7 @@ impl MultiThreadedExecutor { self.dependents_scratch .extend_from_slice(&self.system_task_metadata[system_index].dependents); - for &dep_idx in self.dependents_scratch.iter() { + for &dep_idx in &self.dependents_scratch { let dependent_meta = &mut self.system_task_metadata[dep_idx]; dependent_meta.dependencies_remaining -= 1; if (dependent_meta.dependencies_remaining == 0) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs index cf33351bf8113..793169741afb9 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs @@ -1,5 +1,5 @@ #[cfg(feature = "trace")] -use bevy_utils::tracing::{info_span, Instrument}; +use bevy_utils::tracing::info_span; use fixedbitset::FixedBitSet; use crate::{ diff --git a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs index 4dd5d570b5269..5d2a610f6b7e7 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs @@ -1,5 +1,5 @@ #[cfg(feature = "trace")] -use bevy_utils::tracing::{info_span, Instrument}; +use bevy_utils::tracing::info_span; use fixedbitset::FixedBitSet; use crate::{ diff --git a/crates/bevy_ecs/src/schedule_v3/graph_utils.rs b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs index 6603e5ec68d33..2450fc8dae043 100644 --- a/crates/bevy_ecs/src/schedule_v3/graph_utils.rs +++ b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs @@ -125,7 +125,7 @@ where // iterate nodes in topological order for (i, &node) in topological_order.iter().enumerate() { map.insert(node, i); - topsorted.add_node(node.clone()); + 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, ()); @@ -164,7 +164,7 @@ where let successors = transitive_closure .neighbors_directed(b, Direction::Outgoing) .collect::>(); - for c in successors.into_iter() { + for c in successors { let index_c = *map.get(&c).unwrap(); debug_assert!(index_b < index_c); if !visited[index_c] { diff --git a/crates/bevy_ecs/src/schedule_v3/mod.rs b/crates/bevy_ecs/src/schedule_v3/mod.rs index a0b500c919d3e..8b9e12f83ca6e 100644 --- a/crates/bevy_ecs/src/schedule_v3/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/mod.rs @@ -18,8 +18,6 @@ pub use self::state::*; #[cfg(test)] mod tests { - use bevy_utils::HashSet; - use super::*; use crate as bevy_ecs; use crate::system::*; diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index 46085e0e66bf8..93a689ddd73b2 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -117,6 +117,12 @@ pub struct Schedule { executor: Box, } +impl Default for Schedule { + fn default() -> Self { + Self::new() + } +} + impl Schedule { pub fn new() -> Self { Self { @@ -187,14 +193,14 @@ impl Schedule { } #[derive(Default)] -struct DAG { +struct Dag { /// A directed graph. graph: DiGraphMap, /// A cached topological ordering of the graph. topsort: Vec, } -impl DAG { +impl Dag { pub fn new() -> Self { Self { graph: DiGraphMap::new(), @@ -231,9 +237,9 @@ struct ScheduleMeta { systems: HashMap, conditions: HashMap>, - hierarchy: DAG, - dependency: DAG, - dependency_flattened: DAG, + hierarchy: Dag, + dependency: Dag, + dependency_flattened: Dag, ambiguous_with: UnGraphMap, ambiguous_with_flattened: UnGraphMap, @@ -252,9 +258,9 @@ impl ScheduleMeta { system_sets: HashMap::new(), systems: HashMap::new(), conditions: HashMap::new(), - hierarchy: DAG::new(), - dependency: DAG::new(), - dependency_flattened: DAG::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(), @@ -483,14 +489,14 @@ impl ScheduleMeta { debug_assert!(id.is_system()); system.initialize(world); self.systems.insert(id, system); - for condition in conditions.iter_mut() { + for condition in &mut conditions { condition.initialize(world); } self.conditions.insert(id, conditions); } UninitNode::SystemSet(mut conditions) => { debug_assert!(id.is_set()); - for condition in conditions.iter_mut() { + for condition in &mut conditions { condition.initialize(world); } self.conditions @@ -566,7 +572,7 @@ impl ScheduleMeta { } } - // can't depend on or be ambiguous with system type sets that has many instances + // can't depend on or be ambiguous with system type sets that have many instances for (&set, systems) in systems.iter() { if self.system_sets[&set].is_system_type() { let ambiguities = self.ambiguous_with.edges(set).count(); @@ -601,18 +607,18 @@ impl ScheduleMeta { dependency_flattened.add_edge(lhs, rhs, ()); } (NodeId::Set(_), NodeId::System(_)) => { - for &lhs_ in systems[&lhs].iter() { + for &lhs_ in &systems[&lhs] { dependency_flattened.add_edge(lhs_, rhs, ()); } } (NodeId::System(_), NodeId::Set(_)) => { - for &rhs_ in systems[&rhs].iter() { + for &rhs_ in &systems[&rhs] { dependency_flattened.add_edge(lhs, rhs_, ()); } } (NodeId::Set(_), NodeId::Set(_)) => { - for &lhs_ in systems[&lhs].iter() { - for &rhs_ in systems[&rhs].iter() { + for &lhs_ in &systems[&lhs] { + for &rhs_ in &systems[&rhs] { dependency_flattened.add_edge(lhs_, rhs_, ()); } } @@ -644,18 +650,18 @@ impl ScheduleMeta { ambiguous_with_flattened.add_edge(lhs, rhs, ()); } (NodeId::Set(_), NodeId::System(_)) => { - for &lhs_ in systems[&lhs].iter() { + for &lhs_ in &systems[&lhs] { ambiguous_with_flattened.add_edge(lhs_, rhs, ()); } } (NodeId::System(_), NodeId::Set(_)) => { - for &rhs_ in systems[&rhs].iter() { + for &rhs_ in &systems[&rhs] { ambiguous_with_flattened.add_edge(lhs, rhs_, ()); } } (NodeId::Set(_), NodeId::Set(_)) => { - for &lhs_ in systems[&lhs].iter() { - for &rhs_ in systems[&rhs].iter() { + for &lhs_ in &systems[&lhs] { + for &rhs_ in &systems[&rhs] { ambiguous_with_flattened.add_edge(lhs_, rhs_, ()); } } @@ -682,7 +688,7 @@ impl ScheduleMeta { } else { let access_a = system_a.component_access(); let access_b = system_b.component_access(); - if !access_a.is_compatible(&access_b) { + if !access_a.is_compatible(access_b) { let conflicts = access_a.get_conflicts(access_b); conflicting_systems.push((a, b, conflicts)); } @@ -712,7 +718,7 @@ impl ScheduleMeta { // get the number of dependencies and the immediate dependents of each system // (needed to run systems in the correct order) let mut system_deps = Vec::with_capacity(sys_count); - for &sys_id in dg_system_ids.iter() { + for &sys_id in &dg_system_ids { let num_dependencies = self .dependency_flattened .graph @@ -768,7 +774,7 @@ impl ScheduleMeta { let mut systems_of_sets = vec![FixedBitSet::with_capacity(sys_count); set_count]; for (i, &row) in hg_set_idxs.iter().enumerate() { let bitset = &mut systems_of_sets[i]; - for &(col, sys_id) in hg_systems.iter() { + for &(col, sys_id) in &hg_systems { let idx = dg_system_idx_map[&sys_id]; let is_descendant = result.reachable[index(row, col, node_count)]; bitset.set(idx, is_descendant); @@ -776,7 +782,7 @@ impl ScheduleMeta { } let mut sets_of_systems = vec![FixedBitSet::with_capacity(set_count); sys_count]; - for &(col, sys_id) in hg_systems.iter() { + 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 @@ -832,14 +838,14 @@ impl ScheduleMeta { *schedule = self.build_schedule(); // move systems into new schedule - for &id in schedule.system_ids.iter() { + for &id in &schedule.system_ids { let system = self.systems.remove(&id).unwrap(); let conditions = self.conditions.remove(&id).unwrap(); schedule.systems.push(RefCell::new(system)); schedule.system_conditions.push(RefCell::new(conditions)); } - for &id in schedule.set_ids.iter() { + for &id in &schedule.set_ids { let conditions = self.conditions.remove(&id).unwrap(); schedule.set_conditions.push(RefCell::new(conditions)); } @@ -874,7 +880,7 @@ impl ScheduleMeta { fn report_hierarchy_conflicts(&self, transitive_edges: &[(NodeId, NodeId)]) { // TODO: warn but fix with transitive reduction let mut message = String::from("hierarchy contains redundant edge(s)"); - for (parent, child) in transitive_edges.iter() { + for (parent, child) in transitive_edges { writeln!( message, " -- {:?} '{:?}' cannot be child of set '{:?}', longer path exists", @@ -937,7 +943,7 @@ impl ScheduleMeta { Consider adding `before`, `after`, or `ambiguous_with` relationships between these:\n", ); - for (system_a, system_b, conflicts) in ambiguities.iter() { + 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); From a25017ef73953c6e23567f3cdc20c1484bdae62b Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sat, 19 Nov 2022 21:55:24 -0800 Subject: [PATCH 10/64] fix `is_apply_system_buffers` not working let me know if there's an easier way to do this --- crates/bevy_ecs/src/schedule_v3/executor/mod.rs | 11 ++++------- .../bevy_ecs/src/system/exclusive_function_system.rs | 7 ++++++- crates/bevy_ecs/src/system/function_system.rs | 7 ++++++- crates/bevy_ecs/src/system/system.rs | 4 ++++ crates/bevy_ecs/src/system/system_piping.rs | 6 +++++- crates/bevy_time/src/fixed_timestep.rs | 4 ++++ 6 files changed, 29 insertions(+), 10 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs index 3d00d829fb039..c8a386bdb80a8 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs @@ -6,14 +6,13 @@ pub use self::multi_threaded::MultiThreadedExecutor; pub use self::simple::SimpleExecutor; pub use self::single_threaded::SingleThreadedExecutor; -use std::any::{Any, TypeId}; use std::cell::RefCell; use fixedbitset::FixedBitSet; use crate::{ schedule_v3::{BoxedCondition, NodeId}, - system::{BoxedSystem, IntoSystem}, + system::BoxedSystem, world::World, }; @@ -78,9 +77,7 @@ pub fn apply_system_buffers(world: &mut World) {} /// Returns `true` if the [`System`] is an instance of [`apply_system_buffers`]. pub(super) fn is_apply_system_buffers(system: &BoxedSystem) -> bool { - fn get_type_id(_: &T) -> TypeId { - TypeId::of::() - } - let type_id = get_type_id(&IntoSystem::into_system(apply_system_buffers)); - (&*system as &dyn Any).type_id() == type_id + 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/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index c4146dcb0362e..1d5c7fca36781 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -11,7 +11,7 @@ use crate::{ world::{World, WorldId}, }; use bevy_ecs_macros::all_tuples; -use std::{borrow::Cow, marker::PhantomData}; +use std::{any::TypeId, borrow::Cow, marker::PhantomData}; /// A function system that runs with exclusive [`World`] access. /// @@ -72,6 +72,11 @@ where self.system_meta.name.clone() } + #[inline] + fn type_id(&self) -> TypeId { + TypeId::of::() + } + #[inline] fn component_access(&self) -> &Access { self.system_meta.component_access_set.combined_access() diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 6e0b5661457ef..c46cab36cb215 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -9,7 +9,7 @@ use crate::{ world::{World, WorldId}, }; use bevy_ecs_macros::all_tuples; -use std::{borrow::Cow, fmt::Debug, marker::PhantomData}; +use std::{any::TypeId, borrow::Cow, fmt::Debug, marker::PhantomData}; /// The metadata of a [`System`]. #[derive(Clone)] @@ -368,6 +368,11 @@ where self.system_meta.name.clone() } + #[inline] + fn type_id(&self) -> TypeId { + TypeId::of::() + } + #[inline] fn component_access(&self) -> &Access { self.system_meta.component_access_set.combined_access() diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 7a2063a2b3df8..a6034f0d53829 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -5,6 +5,8 @@ use crate::{ archetype::ArchetypeComponentId, change_detection::MAX_CHANGE_AGE, component::ComponentId, query::Access, schedule::SystemLabelId, world::World, }; + +use std::any::TypeId; use std::borrow::Cow; /// An ECS system that can be added to a [`Schedule`](crate::schedule::Schedule) @@ -26,6 +28,8 @@ pub trait System: Send + Sync + 'static { type Out; /// Returns the system's name. fn name(&self) -> Cow<'static, str>; + /// Returns the [`TypeId`] of the underlying system type. + fn type_id(&self) -> TypeId; /// Returns the system's component [`Access`]. fn component_access(&self) -> &Access; /// Returns the system's archetype component [`Access`]. diff --git a/crates/bevy_ecs/src/system/system_piping.rs b/crates/bevy_ecs/src/system/system_piping.rs index cfc209819dbeb..efd545a46a30f 100644 --- a/crates/bevy_ecs/src/system/system_piping.rs +++ b/crates/bevy_ecs/src/system/system_piping.rs @@ -5,7 +5,7 @@ use crate::{ system::{IntoSystem, System}, world::World, }; -use std::borrow::Cow; +use std::{any::TypeId, borrow::Cow}; /// A [`System`] created by piping the output of the first system into the input of the second. /// @@ -77,6 +77,10 @@ impl> System for PipeSystem< self.name.clone() } + fn type_id(&self) -> TypeId { + TypeId::of::<(SystemA, SystemB)>() + } + fn archetype_component_access(&self) -> &Access { &self.archetype_component_access } diff --git a/crates/bevy_time/src/fixed_timestep.rs b/crates/bevy_time/src/fixed_timestep.rs index 8a63f67f92bb6..fa355706581bb 100644 --- a/crates/bevy_time/src/fixed_timestep.rs +++ b/crates/bevy_time/src/fixed_timestep.rs @@ -177,6 +177,10 @@ impl System for FixedTimestep { Cow::Borrowed(std::any::type_name::()) } + fn type_id(&self) -> std::any::TypeId { + std::any::TypeId::of::() + } + fn archetype_component_access(&self) -> &Access { self.internal_system.archetype_component_access() } From 26d29a57cda67d0322deb85cadfcfbdd14ab5c65 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 20 Nov 2022 10:54:43 -0800 Subject: [PATCH 11/64] docs docs docs docs docs --- .../bevy_ecs/src/schedule_v3/executor/mod.rs | 6 +- crates/bevy_ecs/src/schedule_v3/migration.rs | 20 ++- crates/bevy_ecs/src/schedule_v3/mod.rs | 10 -- crates/bevy_ecs/src/schedule_v3/schedule.rs | 166 +++++++++--------- crates/bevy_ecs/src/schedule_v3/state.rs | 32 ++-- crates/bevy_ecs/src/world/mod.rs | 2 +- 6 files changed, 119 insertions(+), 117 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs index c8a386bdb80a8..b5fa3605eb46b 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs @@ -23,13 +23,17 @@ pub(super) trait SystemExecutor: Send + Sync { fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World); } -/// Controls how a [`Schedule`] will be run. +/// 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. SingleThreaded, /// Like [`SingleThreaded`](ExecutorKind::SingleThreaded) but calls [`apply_buffers`](crate::system::System::apply_buffers) /// immediately after running each system. Simple, + #[default] /// Runs the schedule using a thread pool. Non-conflicting systems can run in parallel. MultiThreaded, } diff --git a/crates/bevy_ecs/src/schedule_v3/migration.rs b/crates/bevy_ecs/src/schedule_v3/migration.rs index 0587363404f93..ec54941c9c4a4 100644 --- a/crates/bevy_ecs/src/schedule_v3/migration.rs +++ b/crates/bevy_ecs/src/schedule_v3/migration.rs @@ -3,26 +3,30 @@ use crate::world::World; /// New "stageless" [`App`](bevy_app::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; - fn edit_schedule( - &mut self, - label: impl ScheduleLabel, - f: impl FnMut(&mut Schedule), - ) -> &mut Self; + /// Sets the [`Schedule`] that will be modified by default within the scope of `f` and calls it. + /// Afterwards, restores the default to its previous value. + /// + /// **Note:** This will create the schedule if it does not already exist. + fn edit_schedule(&mut self, label: impl ScheduleLabel, f: impl FnMut(&mut Self)) -> &mut Self; } /// New "stageless" [`World`] methods. pub trait WorldExt { - /// Runs the [`Schedule`] associated with the provided [`ScheduleLabel`]. + /// 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) { - let mut schedule = self.resource_mut::().check_out(&label).unwrap(); + let mut schedule = self.resource_mut::().remove(&label).unwrap(); schedule.run(self); self.resource_mut::() - .check_in(&label, schedule) + .insert(label, schedule) .unwrap(); } } diff --git a/crates/bevy_ecs/src/schedule_v3/mod.rs b/crates/bevy_ecs/src/schedule_v3/mod.rs index 8b9e12f83ca6e..130c75e73d519 100644 --- a/crates/bevy_ecs/src/schedule_v3/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/mod.rs @@ -223,14 +223,4 @@ mod tests { let result = schedule.initialize(&mut world); assert!(matches!(result, Err(BuildError::Ambiguity))); } - - #[test] - fn schedule_already_exists() { - let mut schedules = Schedules::new(); - let result = schedules.insert(TestSchedule::X, Schedule::new()); - assert!(matches!(result, Ok(()))); - - let result = schedules.insert(TestSchedule::X, Schedule::new()); - assert!(matches!(result, Err(InsertionError::AlreadyExists(_)))); - } } diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index 93a689ddd73b2..96739508365e1 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -21,79 +21,51 @@ use crate::{ world::World, }; -/// Resource for storing [`Schedule`]s. +/// Resource that stores [`Schedule`]s mapped to [`ScheduleLabel`]s. #[derive(Default, Resource)] pub struct Schedules { - inner: HashMap>, + inner: HashMap, } impl Schedules { + /// Constructs an empty `Schedules` with zero initial capacity. pub fn new() -> Self { Self { inner: HashMap::new(), } } - /// Insert a new labeled schedule into the map. + /// Inserts a labeled schedule into the map. /// - /// # Errors - /// - /// TODO - pub fn insert( - &mut self, - label: impl ScheduleLabel, - schedule: Schedule, - ) -> Result<(), InsertionError> { + /// 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 = label.dyn_clone(); if self.inner.contains_key(&label) { - return Err(InsertionError::AlreadyExists(label)); + warn!("schedule with label {:?} already exists", label); } - self.inner.insert(label, Some(schedule)); - Ok(()) + self.inner.insert(label, schedule) } - /// Returns the schedule corresponding to the label. - /// - /// # Errors - /// - /// TODO - pub(crate) fn check_out( - &mut self, - label: &dyn ScheduleLabel, - ) -> Result { + /// Removes the `label` entry from the map, returning its schedule if one existed. + pub fn remove(&mut self, label: &dyn ScheduleLabel) -> Option { let label = label.dyn_clone(); - match self.inner.get_mut(&label) { - Some(container) => match container.take() { - Some(schedule) => Ok(schedule), - None => Err(ExtractionError::AlreadyExtracted(label)), - }, - None => Err(ExtractionError::Unknown(label)), + if !self.inner.contains_key(&label) { + warn!("schedule with label {:?} not found", label); } + self.inner.remove(&label) } - /// Re-inserts the schedule corresponding to the label into the map. - /// - /// The schedule must have been previously extracted using [`check_out`](#method.check_out). - /// - /// # Errors - /// - /// TODO - pub(crate) fn check_in( - &mut self, - label: &dyn ScheduleLabel, - schedule: Schedule, - ) -> Result<(), ExtractionError> { + /// Returns a reference to the schedule associated with `label`, if it exists. + pub fn get(&self, label: &dyn ScheduleLabel) -> Option<&Schedule> { let label = label.dyn_clone(); - match self.inner.get_mut(&label) { - Some(container) => match container.take() { - Some(_) => Err(ExtractionError::NotExtracted(label)), - None => { - *container = Some(schedule); - Ok(()) - } - }, - None => Err(ExtractionError::Unknown(label)), - } + 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> { + let label = label.dyn_clone(); + self.inner.get_mut(&label) } /// Iterates all system change ticks and clamps any older than [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE). @@ -101,16 +73,24 @@ impl Schedules { /// /// **Note:** Does nothing if the [`World`] counter has not been incremented at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD) /// times since the previous pass. - pub(crate) fn check_change_ticks(&mut self, change_tick: u32, last_check_tick: u32) { + pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!("check ticks").entered(); - for schedule in self.inner.values_mut().flatten() { - schedule.check_change_ticks(change_tick, last_check_tick); + let _span = bevy_utils::tracing::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 _span = + bevy_utils::tracing::info_span!("check schedule ticks", name = &name).entered(); + schedule.check_change_ticks(change_tick); } } } -/// TBD +/// A collection of systems, and the metadata and executor needed to run them +/// in a certain order under certain conditions. pub struct Schedule { graph: ScheduleMeta, executable: SystemSchedule, @@ -124,6 +104,7 @@ impl Default for Schedule { } impl Schedule { + /// Constructs an empty `Schedule`. pub fn new() -> Self { Self { graph: ScheduleMeta::new(), @@ -132,43 +113,57 @@ impl Schedule { } } + /// Add a system to the schedule. pub fn add_system

(&mut self, system: impl IntoSystemConfig

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

(&mut self, systems: impl IntoSystemConfigs

) { self.graph.add_systems(systems); } + /// Configure a system set in this schedule. pub fn configure_set(&mut self, set: impl IntoSystemSetConfig) { self.graph.configure_set(set); } + /// Configure a collection of system sets in this schedule. pub fn configure_sets(&mut self, sets: impl IntoSystemSetConfigs) { self.graph.configure_sets(sets); } + /// Sets 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) { self.graph.set_default_set(set); } + /// 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) { - self.executor = match executor { - ExecutorKind::Simple => Box::new(SimpleExecutor::new()), - ExecutorKind::SingleThreaded => Box::new(SingleThreadedExecutor::new()), - ExecutorKind::MultiThreaded => Box::new(MultiThreadedExecutor::new()), - }; + 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.init(&self.executable); + } } + /// Runs all systems in this schedule on the `world`, using its current execution strategy. pub fn run(&mut self, world: &mut World) { self.initialize(world).unwrap(); self.executor.run(&mut self.executable, world); } + /// Initializes all uninitialized systems and conditions, rebuilds the executable schedule, + /// and re-initializes executor data structures. pub(crate) fn initialize(&mut self, world: &mut World) -> Result<(), BuildError> { if self.graph.changed { self.graph.initialize(world); @@ -185,13 +180,26 @@ impl Schedule { /// /// **Note:** Does nothing if the [`World`] counter has not been incremented at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD) /// times since the previous pass. - pub(crate) fn check_change_ticks(&mut self, _change_tick: u32, _last_change_tick: u32) { - #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!("check schedule ticks").entered(); - todo!(); + pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { + for system in self.executable.systems.iter_mut() { + system.borrow_mut().check_change_tick(change_tick); + } + + for conditions in self.executable.system_conditions.iter_mut() { + for system in conditions.borrow_mut().iter_mut() { + system.check_change_tick(change_tick); + } + } + + for conditions in self.executable.set_conditions.iter_mut() { + for system in conditions.borrow_mut().iter_mut() { + system.check_change_tick(change_tick); + } + } } } +/// A directed acylic graph structure. #[derive(Default)] struct Dag { /// A directed graph. @@ -201,7 +209,7 @@ struct Dag { } impl Dag { - pub fn new() -> Self { + fn new() -> Self { Self { graph: DiGraphMap::new(), topsort: Vec::new(), @@ -209,6 +217,7 @@ impl Dag { } } +/// Metadata for a [`SystemSet`], stored in a [`ScheduleMeta`]. struct SystemSetMeta(BoxedSystemSet); impl SystemSetMeta { @@ -225,11 +234,13 @@ impl SystemSetMeta { } } +/// Uninitialized systems associated with a graph node, stored in a [`ScheduleMeta`]. enum UninitNode { System(BoxedSystem, Vec), SystemSet(Vec), } +/// Metadata for a [`Schedule`]. #[derive(Default)] struct ScheduleMeta { system_set_ids: HashMap, @@ -324,7 +335,6 @@ impl ScheduleMeta { // system init has to be deferred (need `&mut World`) self.uninit .push((id, UninitNode::System(system, conditions))); - self.changed = true; Ok(id) } @@ -371,7 +381,6 @@ impl ScheduleMeta { // system init has to be deferred (need `&mut World`) self.uninit.push((id, UninitNode::SystemSet(conditions))); - self.changed = true; Ok(id) } @@ -434,6 +443,7 @@ impl ScheduleMeta { fn update_graphs(&mut self, id: NodeId, graph_info: GraphInfo) -> Result<(), BuildError> { self.check_sets(&id, &graph_info)?; self.check_edges(&id, &graph_info)?; + self.changed = true; let GraphInfo { sets, @@ -716,7 +726,7 @@ impl ScheduleMeta { .collect::>(); // get the number of dependencies and the immediate dependents of each system - // (needed to run systems in the correct order) + // (needed by multi-threaded executor to run systems in the correct order) let mut system_deps = Vec::with_capacity(sys_count); for &sys_id in &dg_system_ids { let num_dependencies = self @@ -963,24 +973,6 @@ impl ScheduleMeta { } } -#[derive(Error, Debug)] -#[non_exhaustive] -pub enum InsertionError { - #[error("schedule `{0:?}` already exists")] - AlreadyExists(BoxedScheduleLabel), -} - -#[derive(Error, Debug)] -#[non_exhaustive] -pub enum ExtractionError { - #[error("unknown schedule: `{0:?}`")] - Unknown(BoxedScheduleLabel), - #[error("schedule `{0:?}` is not available")] - AlreadyExtracted(BoxedScheduleLabel), - #[error("schedule `{0:?}` has not been extracted")] - NotExtracted(BoxedScheduleLabel), -} - #[derive(Error, Debug)] #[non_exhaustive] pub enum BuildError { diff --git a/crates/bevy_ecs/src/schedule_v3/state.rs b/crates/bevy_ecs/src/schedule_v3/state.rs index 782831dffa724..5c68382bc6983 100644 --- a/crates/bevy_ecs/src/schedule_v3/state.rs +++ b/crates/bevy_ecs/src/schedule_v3/state.rs @@ -12,36 +12,48 @@ use crate::world::World; pub trait Statelike: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug {} impl Statelike for T where T: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug {} -/// TBD +/// 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); -/// TBD +/// 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); -/// TBD +/// 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)`]). /// -/// A state transition can be queued through the [`Transition`] resource, and it will -/// be applied by the next [`apply_state_transition::`] system. +/// 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 Transition(pub Option); +pub struct NextState(pub Option); -/// If a state transition is queued in [`Transition`], updates [`State`], then -/// runs the [`OnExit(exited_state)`] and [`OnEnter(entered_state)`] schedules. +/// 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. +/// +/// For simplicity, [`State`] is temporarily removed until the two schedules have completed. pub fn apply_state_transition(world: &mut World) { world.resource_scope(|world, mut state: Mut>| { - if world.resource::>().0.is_some() { - let entered_state = world.resource_mut::>().0.take().unwrap(); + if world.resource::>().0.is_some() { + let entered_state = world.resource_mut::>().0.take().unwrap(); let exited_state = mem::replace(&mut state.0, entered_state.clone()); 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 71036fbbefa7c..9d4d4c442fc3b 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1611,7 +1611,7 @@ impl World { non_send_resources.check_change_ticks(change_tick); if let Some(mut schedules) = self.get_resource_mut::() { - schedules.check_change_ticks(change_tick, last_check_tick); + schedules.check_change_ticks(change_tick); } self.last_check_tick = change_tick; From 1a6a061d02ff481ef40516c6ee42c1efcbf82ba3 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 20 Nov 2022 11:08:22 -0800 Subject: [PATCH 12/64] `explicit_iter_loop` is a dumb lint CMV --- crates/bevy_ecs/src/schedule_v3/schedule.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index 96739508365e1..c9a66ebf8e0ab 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -181,17 +181,17 @@ impl Schedule { /// **Note:** Does nothing if the [`World`] counter has not been incremented at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD) /// times since the previous pass. pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { - for system in self.executable.systems.iter_mut() { + for system in &mut self.executable.systems { system.borrow_mut().check_change_tick(change_tick); } - for conditions in self.executable.system_conditions.iter_mut() { + for conditions in &mut self.executable.system_conditions { for system in conditions.borrow_mut().iter_mut() { system.check_change_tick(change_tick); } } - for conditions in self.executable.set_conditions.iter_mut() { + for conditions in &mut self.executable.set_conditions { for system in conditions.borrow_mut().iter_mut() { system.check_change_tick(change_tick); } From 77d35f16838219d07e7623bb610e702cda9d9866 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 20 Nov 2022 11:39:12 -0800 Subject: [PATCH 13/64] simplify loop --- .../schedule_v3/executor/multi_threaded.rs | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index 00843f3d8b29e..8ec8ccd5565c7 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -56,8 +56,6 @@ pub struct MultiThreadedExecutor { completed_systems: FixedBitSet, /// Systems that have no remaining dependencies and are waiting to run. ready_systems: FixedBitSet, - /// Used to avoid checking systems twice. - seen_ready_systems: FixedBitSet, /// Systems that are currently running. running_systems: FixedBitSet, /// Systems that have run but have not had their buffers applied. @@ -81,10 +79,8 @@ impl SystemExecutor for MultiThreadedExecutor { let set_count = schedule.set_ids.len(); self.completed_sets = FixedBitSet::with_capacity(set_count); - self.completed_systems = FixedBitSet::with_capacity(sys_count); self.ready_systems = FixedBitSet::with_capacity(sys_count); - self.seen_ready_systems = FixedBitSet::with_capacity(sys_count); self.running_systems = FixedBitSet::with_capacity(sys_count); self.unapplied_systems = FixedBitSet::with_capacity(sys_count); @@ -122,11 +118,16 @@ impl SystemExecutor for MultiThreadedExecutor { } } + // spare bitset to avoid repeated allocations + let mut ready = FixedBitSet::with_capacity(self.ready_systems.len()); + // main loop let world = SyncUnsafeCell::from_mut(world); while self.completed_systems.count_ones(..) != self.completed_systems.len() { if !self.exclusive_running { - self.spawn_system_tasks(scope, schedule, world); + ready.clear(); + ready.union_with(&self.ready_systems); + self.spawn_system_tasks(&ready, scope, schedule, world); } if self.running_systems.count_ones(..) > 0 { @@ -182,7 +183,6 @@ impl MultiThreadedExecutor { completed_sets: FixedBitSet::new(), completed_systems: FixedBitSet::new(), ready_systems: FixedBitSet::new(), - seen_ready_systems: FixedBitSet::new(), running_systems: FixedBitSet::new(), unapplied_systems: FixedBitSet::new(), } @@ -190,17 +190,16 @@ impl MultiThreadedExecutor { fn spawn_system_tasks<'scope>( &mut self, + ready: &FixedBitSet, scope: &Scope<'_, 'scope, ()>, schedule: &'scope SystemSchedule, world: &'scope SyncUnsafeCell, ) { - while let Some(system_index) = self - .ready_systems - .difference(&self.seen_ready_systems) - .next() - { - // skip systems we've already seen during this call - self.seen_ready_systems.insert(system_index); + for system_index in ready.ones() { + // systems can be skipped within this loop + if self.completed_systems.contains(system_index) { + continue; + } if !self.system_task_metadata[system_index].is_exclusive { // SAFETY: no exclusive system running @@ -231,8 +230,6 @@ impl MultiThreadedExecutor { break; } } - - self.seen_ready_systems.clear(); } fn spawn_system_task<'scope>( From 385d6bf553844fdccd790b7eedc7d86bd1360662 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 20 Nov 2022 12:01:54 -0800 Subject: [PATCH 14/64] typo --- crates/bevy_ecs/src/schedule_v3/schedule.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index c9a66ebf8e0ab..5600a3f2fd285 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -146,7 +146,7 @@ impl Schedule { /// Sets the schedule's execution strategy. pub fn set_executor_kind(&mut self, executor: ExecutorKind) { - if executor == self.executor.kind() { + if executor != self.executor.kind() { self.executor = match executor { ExecutorKind::Simple => Box::new(SimpleExecutor::new()), ExecutorKind::SingleThreaded => Box::new(SingleThreadedExecutor::new()), From 4ba6a1d32aac836295bd87aa4f6215661e10a84c Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 20 Nov 2022 12:06:59 -0800 Subject: [PATCH 15/64] just leave `State` in the world --- crates/bevy_ecs/src/schedule_v3/state.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/state.rs b/crates/bevy_ecs/src/schedule_v3/state.rs index 5c68382bc6983..bd3240c43b7df 100644 --- a/crates/bevy_ecs/src/schedule_v3/state.rs +++ b/crates/bevy_ecs/src/schedule_v3/state.rs @@ -3,7 +3,6 @@ use std::hash::Hash; use std::mem; use crate as bevy_ecs; -use crate::change_detection::Mut; use crate::schedule_v3::{ScheduleLabel, SystemSet, WorldExt}; use crate::system::Resource; use crate::world::World; @@ -48,15 +47,14 @@ pub struct NextState(pub Option); /// - Takes the new state value from [`NextState`] and updates [`State`]. /// - Runs the [`OnExit(exited_state)`] schedule. /// - Runs the [`OnEnter(entered_state)`] schedule. -/// -/// For simplicity, [`State`] is temporarily removed until the two schedules have completed. pub fn apply_state_transition(world: &mut World) { - world.resource_scope(|world, mut state: Mut>| { - if world.resource::>().0.is_some() { - let entered_state = world.resource_mut::>().0.take().unwrap(); - let exited_state = mem::replace(&mut state.0, entered_state.clone()); - world.run_schedule(OnExit(exited_state)); - world.run_schedule(OnEnter(entered_state)); - } - }); + 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 19fd6c04e8f4262d772e021b6dd5d0c7f98b0cff Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 20 Nov 2022 12:19:28 -0800 Subject: [PATCH 16/64] nit --- crates/bevy_ecs/src/schedule_v3/migration.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/migration.rs b/crates/bevy_ecs/src/schedule_v3/migration.rs index ec54941c9c4a4..31bc76d8e9eec 100644 --- a/crates/bevy_ecs/src/schedule_v3/migration.rs +++ b/crates/bevy_ecs/src/schedule_v3/migration.rs @@ -25,8 +25,6 @@ impl WorldExt for World { fn run_schedule(&mut self, label: impl ScheduleLabel) { let mut schedule = self.resource_mut::().remove(&label).unwrap(); schedule.run(self); - self.resource_mut::() - .insert(label, schedule) - .unwrap(); + self.resource_mut::().insert(label, schedule); } } From 47b3ff4c37fa2f4c5f0e2d749c94e41484ad850c Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 20 Nov 2022 12:22:28 -0800 Subject: [PATCH 17/64] internal consistency --- crates/bevy_ecs/src/schedule_v3/graph_utils.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/graph_utils.rs b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs index 2450fc8dae043..312b2af990ed0 100644 --- a/crates/bevy_ecs/src/schedule_v3/graph_utils.rs +++ b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs @@ -1,12 +1,12 @@ -use crate::schedule_v3::set::*; +use std::fmt::Debug; + use bevy_utils::{ petgraph::{graphmap::NodeTrait, prelude::*}, HashMap, HashSet, }; - use fixedbitset::FixedBitSet; -use std::fmt::Debug; +use crate::schedule_v3::set::*; /// Unique identifier for a system or system set. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] From 54ba8b44dbace8cbf77e1e840d8f0cdad2a3ca25 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 20 Nov 2022 12:29:51 -0800 Subject: [PATCH 18/64] don't panic --- crates/bevy_ecs/src/schedule_v3/migration.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/migration.rs b/crates/bevy_ecs/src/schedule_v3/migration.rs index 31bc76d8e9eec..16b0f31d44092 100644 --- a/crates/bevy_ecs/src/schedule_v3/migration.rs +++ b/crates/bevy_ecs/src/schedule_v3/migration.rs @@ -23,8 +23,9 @@ pub trait WorldExt { impl WorldExt for World { fn run_schedule(&mut self, label: impl ScheduleLabel) { - let mut schedule = self.resource_mut::().remove(&label).unwrap(); - schedule.run(self); - self.resource_mut::().insert(label, schedule); + if let Some(mut schedule) = self.resource_mut::().remove(&label) { + schedule.run(self); + self.resource_mut::().insert(label, schedule); + } } } From 71d463166aa1679f57985398f5af6df14cee23d1 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 20 Nov 2022 14:17:47 -0800 Subject: [PATCH 19/64] cleanup --- .../schedule_v3/executor/multi_threaded.rs | 52 ++++++++----------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index 8ec8ccd5565c7..63e06d8f221c9 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -35,21 +35,18 @@ struct SystemTaskMetadata { pub struct MultiThreadedExecutor { /// Metadata for scheduling and running system tasks. system_task_metadata: Vec, - /// Sends system completion events. sender: Sender, /// Receives system completion events. receiver: Receiver, /// Scratch vector to avoid frequent allocation. dependents_scratch: 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, - /// System sets that have been skipped or had their conditions evaluated. completed_sets: FixedBitSet, /// Systems that have run or been skipped. @@ -108,9 +105,9 @@ impl SystemExecutor for MultiThreadedExecutor { #[cfg(feature = "trace")] let _schedule_span = info_span!("schedule").entered(); ComputeTaskPool::init(TaskPool::default).scope(|scope| { - // the runner itself is a `Send` future so that it can run + // the executor itself is a `Send` future so that it can run // alongside systems that claim the local thread - let runner = async { + let executor = async { // systems with zero dependencies for (index, system_meta) in self.system_task_metadata.iter_mut().enumerate() { if system_meta.dependencies_total == 0 { @@ -161,10 +158,10 @@ impl SystemExecutor for MultiThreadedExecutor { }; #[cfg(feature = "trace")] - let runner_span = info_span!("schedule_task"); + let executor_span = info_span!("schedule_task"); #[cfg(feature = "trace")] - let runner = runner.instrument(runner_span); - scope.spawn(runner); + let executor = executor.instrument(executor_span); + scope.spawn(executor); }); } } @@ -193,7 +190,7 @@ impl MultiThreadedExecutor { ready: &FixedBitSet, scope: &Scope<'_, 'scope, ()>, schedule: &'scope SystemSchedule, - world: &'scope SyncUnsafeCell, + cell: &'scope SyncUnsafeCell, ) { for system_index in ready.ones() { // systems can be skipped within this loop @@ -201,31 +198,24 @@ impl MultiThreadedExecutor { continue; } + // SAFETY: no exclusive system running + let world = unsafe { &*cell.get() }; + if !self.can_run(system_index, schedule, world) { + // NOTE: exclusive systems with ambiguities are susceptible to + // being significantly "delayed" here (compared to single-threaded) + // if systems after them in topological order can run + // if that becomes an issue, `break;` if exclusive system + continue; + } + if !self.should_run(system_index, schedule, world) { + continue; + } + if !self.system_task_metadata[system_index].is_exclusive { - // SAFETY: no exclusive system running - let world = unsafe { &*world.get() }; - if !self.can_run(system_index, schedule, world) { - continue; - } - if !self.should_run(system_index, schedule, world) { - continue; - } self.spawn_system_task(scope, system_index, schedule, world); } else { - { - // SAFETY: no exclusive system running - let world = unsafe { &*world.get() }; - if !self.can_run(system_index, schedule, world) { - // the `break` here emulates single-threaded runner behavior - // without it, exclusive systems would likely be stalled out - break; - } - if !self.should_run(system_index, schedule, world) { - continue; - } - } - // SAFETY: no system running - let world = unsafe { &mut *world.get() }; + // SAFETY: no other system running + let world = unsafe { &mut *cell.get() }; self.spawn_exclusive_system_task(scope, system_index, schedule, world); break; } From cc4157270482347a9dbcaea8effafeab07717499 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 20 Nov 2022 14:53:56 -0800 Subject: [PATCH 20/64] default system set handling for system sets --- crates/bevy_ecs/src/schedule_v3/config.rs | 3 +- crates/bevy_ecs/src/schedule_v3/schedule.rs | 36 +++++++++++++++------ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/config.rs b/crates/bevy_ecs/src/schedule_v3/config.rs index fac15a7dc452c..283f9935f7a10 100644 --- a/crates/bevy_ecs/src/schedule_v3/config.rs +++ b/crates/bevy_ecs/src/schedule_v3/config.rs @@ -61,7 +61,8 @@ 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 `{}` accesses thread-local resources. This is not currently supported.", + condition_system.name() ); Box::new(condition_system) diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index 5600a3f2fd285..e28394b13d150 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -7,7 +7,7 @@ use std::{ use bevy_utils::{ petgraph::{algo::tarjan_scc, prelude::*}, thiserror::Error, - tracing::{error, warn}, + tracing::{error, info, warn}, HashMap, HashSet, }; @@ -218,19 +218,30 @@ impl Dag { } /// Metadata for a [`SystemSet`], stored in a [`ScheduleMeta`]. -struct SystemSetMeta(BoxedSystemSet); +struct SystemSetMeta { + set: BoxedSystemSet, + /// `true` if this system set was modified with `configure_set` + configured: bool, +} impl SystemSetMeta { pub fn new(set: BoxedSystemSet) -> Self { - Self(set) + Self { + set, + configured: false, + } } pub fn name(&self) -> Cow<'static, str> { - format!("{:?}", &self.0).into() + format!("{:?}", &self.set).into() } pub fn is_system_type(&self) -> bool { - self.0.is_system_type() + self.set.is_system_type() + } + + pub fn dyn_clone(&self) -> BoxedSystemSet { + self.set.dyn_clone() } } @@ -366,12 +377,19 @@ impl ScheduleMeta { let id = match self.system_set_ids.get(&set) { Some(&id) => id, - None => self.add_set(set), + None => self.add_set(set.dyn_clone()), }; - // TODO: only if this is the first time configure_set has been called for this set - if graph_info.sets.is_empty() { + let already_configured = self + .system_sets + .get_mut(&id) + .map_or(false, |meta| std::mem::replace(&mut meta.configured, true)); + + // 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.insert(default.dyn_clone()); } } @@ -598,7 +616,7 @@ impl ScheduleMeta { .edges_directed(set, Direction::Outgoing) .count(); if systems.len() > 1 && (ambiguities > 0 || dependencies > 0) { - let type_set = self.system_sets[&set].0.dyn_clone(); + let type_set = self.system_sets[&set].dyn_clone(); return Err(BuildError::SystemTypeSetAmbiguity(type_set)); } } From 0c826fbff8664cb1df6f7548ab27aec7f3a0d9db Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 20 Nov 2022 15:20:45 -0800 Subject: [PATCH 21/64] check docs --- crates/bevy_ecs/src/schedule_v3/condition.rs | 12 ++++++------ crates/bevy_ecs/src/schedule_v3/executor/mod.rs | 4 ++-- crates/bevy_ecs/src/schedule_v3/migration.rs | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/condition.rs b/crates/bevy_ecs/src/schedule_v3/condition.rs index cfcd02439545c..aeae8bdf51085 100644 --- a/crates/bevy_ecs/src/schedule_v3/condition.rs +++ b/crates/bevy_ecs/src/schedule_v3/condition.rs @@ -33,7 +33,7 @@ pub mod helper { use crate::schedule_v3::{State, Statelike}; use crate::system::{Res, Resource}; - /// Generates a [`Condition`]-satisfying closure that returns `true` + /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if the resource exists. pub fn resource_exists() -> impl FnMut(Option>) -> bool where @@ -42,7 +42,7 @@ pub mod helper { move |res: Option>| res.is_some() } - /// Generates a [`Condition`]-satisfying closure that returns `true` + /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if the resource is equal to `value`. /// /// # Panics @@ -55,7 +55,7 @@ pub mod helper { move |res: Res| *res == value } - /// Generates a [`Condition`]-satisfying closure that returns `true` + /// 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. @@ -69,13 +69,13 @@ pub mod helper { } } - /// Generates a [`Condition`]-satisfying closure that returns `true` + /// 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`]-satisfying closure that returns `true` + /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if the state machine is currently in `state`. /// /// # Panics @@ -85,7 +85,7 @@ pub mod helper { move |current_state: Res>| current_state.0 == state } - /// Generates a [`Condition`]-satisfying closure that returns `true` + /// 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. diff --git a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs index b5fa3605eb46b..96c693f73e06c 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs @@ -75,11 +75,11 @@ unsafe impl Sync for SystemSchedule {} /// /// **Notes** /// - This function (currently) does nothing if it's called manually or wrapped inside a [`PipeSystem`](crate::system::PipeSystem). -/// - Modifying a [`Schedule`] may change the order buffers are applied. +/// - 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`] is an instance of [`apply_system_buffers`]. +/// 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` diff --git a/crates/bevy_ecs/src/schedule_v3/migration.rs b/crates/bevy_ecs/src/schedule_v3/migration.rs index 16b0f31d44092..7c27e1815a7cb 100644 --- a/crates/bevy_ecs/src/schedule_v3/migration.rs +++ b/crates/bevy_ecs/src/schedule_v3/migration.rs @@ -1,7 +1,7 @@ use crate::schedule_v3::*; use crate::world::World; -/// New "stageless" [`App`](bevy_app::App) methods. +/// New "stageless" `App` methods. pub trait AppExt { /// Sets the [`Schedule`] that will be modified by default when you call `App::add_system` /// and similar methods. From 20eb90520efd9a23ba179f1e5d971a52fcb856f4 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sun, 20 Nov 2022 22:24:45 -0800 Subject: [PATCH 22/64] Make signalling dependents copyless --- .../schedule_v3/executor/multi_threaded.rs | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index 63e06d8f221c9..7dec396cfff58 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -20,8 +20,6 @@ struct SystemTaskMetadata { dependents: Vec, /// The number of dependencies the system has in total. dependencies_total: usize, - /// The number of dependencies the system has that have not completed. - dependencies_remaining: usize, // These values are cached because we can't read them from the system while it's running. /// The `ArchetypeComponentId` access of the system. archetype_component_access: Access, @@ -39,8 +37,8 @@ pub struct MultiThreadedExecutor { sender: Sender, /// Receives system completion events. receiver: Receiver, - /// Scratch vector to avoid frequent allocation. - dependents_scratch: Vec, + /// The number of dependencies the system has that have not completed. + dependencies_remaining: Vec, /// Union of the accesses of all currently running systems. active_access: Access, /// Returns `true` if a system with non-`Send` access is running. @@ -81,16 +79,16 @@ impl SystemExecutor for MultiThreadedExecutor { self.running_systems = FixedBitSet::with_capacity(sys_count); self.unapplied_systems = FixedBitSet::with_capacity(sys_count); - self.dependents_scratch = Vec::with_capacity(sys_count); + self.dependencies_remaining = Vec::with_capacity(sys_count); self.system_task_metadata = Vec::with_capacity(sys_count); for index in 0..sys_count { let (num_dependencies, dependents) = schedule.system_deps[index].clone(); let system = schedule.systems[index].borrow(); + self.dependencies_remaining.push(num_dependencies); self.system_task_metadata.push(SystemTaskMetadata { dependents, dependencies_total: num_dependencies, - dependencies_remaining: num_dependencies, is_send: system.is_send(), is_exclusive: system.is_exclusive(), archetype_component_access: default(), @@ -173,7 +171,7 @@ impl MultiThreadedExecutor { system_task_metadata: Vec::new(), sender, receiver, - dependents_scratch: Vec::new(), + dependencies_remaining: Vec::new(), active_access: default(), local_thread_running: false, exclusive_running: false, @@ -472,20 +470,14 @@ impl MultiThreadedExecutor { fn signal_dependents(&mut self, system_index: usize) { #[cfg(feature = "trace")] let _span = info_span!("signal_dependents").entered(); - self.dependents_scratch - .extend_from_slice(&self.system_task_metadata[system_index].dependents); - - for &dep_idx in &self.dependents_scratch { - let dependent_meta = &mut self.system_task_metadata[dep_idx]; - dependent_meta.dependencies_remaining -= 1; - if (dependent_meta.dependencies_remaining == 0) - && !self.completed_systems.contains(dep_idx) - { + + for &dep_idx in &self.system_task_metadata[system_index].dependents { + let dependencies = &mut self.dependencies_remaining[dep_idx]; + *dependencies -= 1; + if *dependencies == 0 && !self.completed_systems.contains(dep_idx) { self.ready_systems.insert(dep_idx); } } - - self.dependents_scratch.clear(); } fn rebuild_active_access(&mut self) { From dc5abef3dd4ee1da50be2282ccb982c730f6ee60 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Mon, 21 Nov 2022 09:24:13 -0800 Subject: [PATCH 23/64] Apply suggestions from code review Co-authored-by: James Liu --- crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index 7dec396cfff58..ccb8aefe33aaa 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -18,8 +18,6 @@ use crate::{ struct SystemTaskMetadata { /// Indices of the systems that directly depend on the system. dependents: Vec, - /// The number of dependencies the system has in total. - dependencies_total: usize, // These values are cached because we can't read them from the system while it's running. /// The `ArchetypeComponentId` access of the system. archetype_component_access: Access, @@ -107,8 +105,8 @@ impl SystemExecutor for MultiThreadedExecutor { // alongside systems that claim the local thread let executor = async { // systems with zero dependencies - for (index, system_meta) in self.system_task_metadata.iter_mut().enumerate() { - if system_meta.dependencies_total == 0 { + for (index, dependencies) in self.dependencies_remaining.iter().enumerate() { + if *dependencies == 0 { self.ready_systems.insert(index); } } From c899e994364c65595d937d5885cbbea22159baf1 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Mon, 21 Nov 2022 09:32:36 -0800 Subject: [PATCH 24/64] nits --- crates/bevy_ecs/src/schedule_v3/executor/mod.rs | 5 ++++- .../src/schedule_v3/executor/multi_threaded.rs | 12 +++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs index 96c693f73e06c..85e37f832e6d0 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs @@ -67,7 +67,10 @@ impl SystemSchedule { } } -// SAFETY: MultiThreadedExecutor does not alias RefCell instances +// SAFETY: +// - MultiThreadedExecutor is the only type that uses SystemSchedule across multiple threads. +// - MultiThreadedExecutor cannot alias any of the RefCells. +// - SystemSchedule is not made pub in any way unsafe impl Sync for SystemSchedule {} /// Instructs the executor to call [`apply_buffers`](crate::system::System::apply_buffers) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index ccb8aefe33aaa..4f5c473933c71 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -86,7 +86,6 @@ impl SystemExecutor for MultiThreadedExecutor { self.dependencies_remaining.push(num_dependencies); self.system_task_metadata.push(SystemTaskMetadata { dependents, - dependencies_total: num_dependencies, is_send: system.is_send(), is_exclusive: system.is_exclusive(), archetype_component_access: default(), @@ -123,7 +122,7 @@ impl SystemExecutor for MultiThreadedExecutor { self.spawn_system_tasks(&ready, scope, schedule, world); } - if self.running_systems.count_ones(..) > 0 { + if !self.running_systems.is_clear() { // wait for systems to complete let index = self .receiver @@ -145,9 +144,9 @@ impl SystemExecutor for MultiThreadedExecutor { let world = unsafe { &mut *world.get() }; Self::apply_system_buffers(&mut self.unapplied_systems, schedule, world); - debug_assert_eq!(self.ready_systems.count_ones(..), 0); - debug_assert_eq!(self.running_systems.count_ones(..), 0); - debug_assert_eq!(self.unapplied_systems.count_ones(..), 0); + 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.completed_sets.clear(); self.completed_systems.clear(); @@ -198,7 +197,7 @@ impl MultiThreadedExecutor { let world = unsafe { &*cell.get() }; if !self.can_run(system_index, schedule, world) { // NOTE: exclusive systems with ambiguities are susceptible to - // being significantly "delayed" here (compared to single-threaded) + // 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; @@ -468,7 +467,6 @@ impl MultiThreadedExecutor { 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 dependencies = &mut self.dependencies_remaining[dep_idx]; *dependencies -= 1; From 730182d3d1b7ce40a9b468d52a7a0b5f4cb33c7c Mon Sep 17 00:00:00 2001 From: Michael Hsu Date: Sun, 20 Nov 2022 18:48:56 -0800 Subject: [PATCH 25/64] add some tests --- crates/bevy_ecs/src/schedule_v3/schedule.rs | 258 ++++++++++++++++++++ 1 file changed, 258 insertions(+) diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index e28394b13d150..3820a213c9a71 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -1013,3 +1013,261 @@ pub enum BuildError { #[error("schedule not initialized")] Uninitialized, } + +#[cfg(test)] +mod tests { + 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 Set { + A, + B, + C, + D, + } + + #[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 std::sync::{Arc, Barrier}; + + 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] + fn parallel_execution() { + let mut world = World::default(); + let mut schedule = Schedule::default(); + + let barrier = Arc::new(Barrier::new(3)); + + let barrier1 = barrier.clone(); + schedule.add_system(move || { + barrier1.wait(); + }); + let barrier2 = barrier.clone(); + schedule.add_system(move || { + barrier2.wait(); + }); + schedule.add_system(move || { + barrier.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(Set::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(Set::A.after(named_system)); + schedule.add_system(make_function_system(3).before(Set::A).after(named_system)); + schedule.add_system(make_function_system(4).after(Set::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]); + } + } + + mod run_conditions { + use super::*; + + #[test] + fn system_with_run_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_run_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_run_criteria_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_run_criteria_on_system_sets() { + let mut world = World::default(); + let mut schedule = Schedule::default(); + + world.init_resource::(); + + schedule.configure_set(Set::A.run_if(|| false).run_if(|| false)); + schedule.add_system(counting_system.in_set(Set::A)); + schedule.configure_set(Set::B.run_if(|| true).run_if(|| false)); + schedule.add_system(counting_system.in_set(Set::B)); + schedule.configure_set(Set::C.run_if(|| false).run_if(|| true)); + schedule.add_system(counting_system.in_set(Set::C)); + schedule.configure_set(Set::D.run_if(|| true).run_if(|| true)); + schedule.add_system(counting_system.in_set(Set::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(Set::A.run_if(|| false)); + schedule.add_system(counting_system.in_set(Set::A).run_if(|| false)); + schedule.configure_set(Set::B.run_if(|| true)); + schedule.add_system(counting_system.in_set(Set::B).run_if(|| false)); + schedule.configure_set(Set::C.run_if(|| false)); + schedule.add_system(counting_system.in_set(Set::C).run_if(|| true)); + schedule.configure_set(Set::D.run_if(|| true)); + schedule.add_system(counting_system.in_set(Set::D).run_if(|| true)); + + schedule.run(&mut world); + assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); + } + } + + mod system_ambiguity_errors { + // TODO + } + + mod schedule_build_errors { + // TODO errors other than system ambiguity errors + } +} From 4c2af4482f4ad671b61e919b712b17949f2a1b33 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Mon, 21 Nov 2022 09:25:07 -0800 Subject: [PATCH 26/64] move tests to mod.rs --- crates/bevy_ecs/src/schedule_v3/mod.rs | 539 ++++++++++++++------ crates/bevy_ecs/src/schedule_v3/schedule.rs | 258 ---------- 2 files changed, 386 insertions(+), 411 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/mod.rs b/crates/bevy_ecs/src/schedule_v3/mod.rs index 130c75e73d519..d06b86b95d7a7 100644 --- a/crates/bevy_ecs/src/schedule_v3/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/mod.rs @@ -19,9 +19,12 @@ pub use self::state::*; #[cfg(test)] mod tests { use super::*; - use crate as bevy_ecs; - use crate::system::*; - use crate::world::World; + 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}; #[allow(dead_code)] #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] @@ -42,185 +45,415 @@ mod tests { X, } - #[test] - fn correct_order() { - #[derive(Resource)] - struct X(Vec); - - let mut world = World::new(); - world.insert_resource(X(Vec::new())); - - fn run(set: TestSet) -> impl FnMut(ResMut) { - move |mut x: ResMut| { - x.0.push(set.clone()); - } - } - - let mut schedule = Schedule::new(); - schedule.add_systems( - ( - run(TestSet::A), - run(TestSet::B), - run(TestSet::C), - run(TestSet::D), - ) - .chain(), - ); - - schedule.run(&mut world); - let X(results) = world.remove_resource::().unwrap(); - assert_eq!( - results, - vec![TestSet::A, TestSet::B, TestSet::C, TestSet::D] - ); + #[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) } - #[test] - #[should_panic] - fn dependency_loop() { - let mut schedule = Schedule::new(); - schedule.configure_set(TestSet::X.after(TestSet::X)); + fn make_function_system(tag: u32) -> impl FnMut(ResMut) { + move |mut resource: ResMut| resource.0.push(tag) } - #[test] - fn dependency_cycle() { - let mut world = World::new(); - let mut schedule = Schedule::new(); + fn named_system(mut resource: ResMut) { + resource.0.push(u32::MAX); + } - schedule.configure_set(TestSet::A.after(TestSet::B)); - schedule.configure_set(TestSet::B.after(TestSet::A)); + fn named_exclusive_system(world: &mut World) { + world.resource_mut::().0.push(u32::MAX); + } - let result = schedule.initialize(&mut world); - assert!(matches!(result, Err(BuildError::DependencyCycle))); + fn counting_system(counter: Res) { + counter.0.fetch_add(1, Ordering::Relaxed); + } - fn foo() {} - fn bar() {} + mod system_execution { + use std::sync::{Arc, Barrier}; - let mut world = World::new(); - let mut schedule = Schedule::new(); + use super::*; - schedule.add_systems((foo.after(bar), bar.after(foo))); - let result = schedule.initialize(&mut world); - assert!(matches!(result, Err(BuildError::DependencyCycle))); - } + #[test] + fn run_system() { + let mut world = World::default(); + let mut schedule = Schedule::default(); - #[test] - #[should_panic] - fn hierarchy_loop() { - let mut schedule = Schedule::new(); - schedule.configure_set(TestSet::X.in_set(TestSet::X)); - } + world.init_resource::(); - #[test] - fn hierarchy_cycle() { - let mut world = World::new(); - let mut schedule = Schedule::new(); + schedule.add_system(make_function_system(0)); + schedule.run(&mut world); - schedule.configure_set(TestSet::A.in_set(TestSet::B)); - schedule.configure_set(TestSet::B.in_set(TestSet::A)); + assert_eq!(world.resource::().0, vec![0]); + } - let result = schedule.initialize(&mut world); - assert!(matches!(result, Err(BuildError::HierarchyCycle))); - } + #[test] + fn run_exclusive_system() { + let mut world = World::default(); + let mut schedule = Schedule::default(); - #[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(BuildError::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(BuildError::SystemTypeSetAmbiguity(_)))); - } + world.init_resource::(); - #[test] - #[should_panic] - fn in_system_type_set() { - fn foo() {} - fn bar() {} + schedule.add_system(make_exclusive_system(0)); + schedule.run(&mut world); - let mut schedule = Schedule::new(); - schedule.add_system(foo.in_set(bar.into_system_set())); - } + assert_eq!(world.resource::().0, vec![0]); + } - #[test] - #[should_panic] - fn configure_system_type_set() { - fn foo() {} - let mut schedule = Schedule::new(); - schedule.configure_set(foo.into_system_set()); + #[test] + fn parallel_execution() { + let mut world = World::default(); + let mut schedule = Schedule::default(); + + let barrier = Arc::new(Barrier::new(3)); + + let barrier1 = barrier.clone(); + schedule.add_system(move || { + barrier1.wait(); + }); + let barrier2 = barrier.clone(); + schedule.add_system(move || { + barrier2.wait(); + }); + schedule.add_system(move || { + barrier.wait(); + }); + + schedule.run(&mut world); + } } - #[test] - fn hierarchy_conflict() { - let mut world = World::new(); - let mut schedule = Schedule::new(); + mod system_ordering { + use super::*; - // Add `A`. - schedule.configure_set(TestSet::A); + #[test] + fn order_systems() { + let mut world = World::default(); + let mut schedule = Schedule::default(); - // Add `B` as child of `A`. - schedule.configure_set(TestSet::B.in_set(TestSet::A)); + world.init_resource::(); - // Add `X` as child of both `A` and `B`. - schedule.configure_set(TestSet::X.in_set(TestSet::A).in_set(TestSet::B)); + 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); - // `X` cannot be the `A`'s child and grandchild at the same time. - let result = schedule.initialize(&mut world); - assert!(matches!(result, Err(BuildError::HierarchyConflict))); - } + assert_eq!(world.resource::().0, vec![1, u32::MAX, 0]); - #[test] - fn cross_dependency() { - let mut world = World::new(); - let mut schedule = Schedule::new(); + world.insert_resource(SystemOrder::default()); - // 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(BuildError::CrossDependency(_, _)))); + 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]); + } } - #[test] - fn ambiguity() { - #[derive(Resource)] - struct X; + mod run_conditions { + use super::*; + + #[test] + fn system_with_run_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_run_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_run_criteria_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_run_criteria_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::(); - fn res_ref(_x: Res) {} - fn res_mut(_x: ResMut) {} + 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); + } + } - let mut world = World::new(); - let mut schedule = Schedule::new(); + 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(BuildError::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(BuildError::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(BuildError::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(BuildError::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(BuildError::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_conflict() { + let mut world = World::new(); + let mut schedule = Schedule::new(); + + // 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(BuildError::HierarchyConflict))); + } + + #[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(BuildError::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.add_systems((res_ref, res_mut)); + let result = schedule.initialize(&mut world); + assert!(matches!(result, Err(BuildError::Ambiguity))); + } + } - schedule.add_systems((res_ref, res_mut)); - let result = schedule.initialize(&mut world); - assert!(matches!(result, Err(BuildError::Ambiguity))); + mod system_ambiguity_errors { + // TODO } } diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index 3820a213c9a71..e28394b13d150 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -1013,261 +1013,3 @@ pub enum BuildError { #[error("schedule not initialized")] Uninitialized, } - -#[cfg(test)] -mod tests { - 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 Set { - A, - B, - C, - D, - } - - #[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 std::sync::{Arc, Barrier}; - - 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] - fn parallel_execution() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - let barrier = Arc::new(Barrier::new(3)); - - let barrier1 = barrier.clone(); - schedule.add_system(move || { - barrier1.wait(); - }); - let barrier2 = barrier.clone(); - schedule.add_system(move || { - barrier2.wait(); - }); - schedule.add_system(move || { - barrier.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(Set::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(Set::A.after(named_system)); - schedule.add_system(make_function_system(3).before(Set::A).after(named_system)); - schedule.add_system(make_function_system(4).after(Set::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]); - } - } - - mod run_conditions { - use super::*; - - #[test] - fn system_with_run_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_run_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_run_criteria_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_run_criteria_on_system_sets() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - world.init_resource::(); - - schedule.configure_set(Set::A.run_if(|| false).run_if(|| false)); - schedule.add_system(counting_system.in_set(Set::A)); - schedule.configure_set(Set::B.run_if(|| true).run_if(|| false)); - schedule.add_system(counting_system.in_set(Set::B)); - schedule.configure_set(Set::C.run_if(|| false).run_if(|| true)); - schedule.add_system(counting_system.in_set(Set::C)); - schedule.configure_set(Set::D.run_if(|| true).run_if(|| true)); - schedule.add_system(counting_system.in_set(Set::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(Set::A.run_if(|| false)); - schedule.add_system(counting_system.in_set(Set::A).run_if(|| false)); - schedule.configure_set(Set::B.run_if(|| true)); - schedule.add_system(counting_system.in_set(Set::B).run_if(|| false)); - schedule.configure_set(Set::C.run_if(|| false)); - schedule.add_system(counting_system.in_set(Set::C).run_if(|| true)); - schedule.configure_set(Set::D.run_if(|| true)); - schedule.add_system(counting_system.in_set(Set::D).run_if(|| true)); - - schedule.run(&mut world); - assert_eq!(world.resource::().0.load(Ordering::Relaxed), 1); - } - } - - mod system_ambiguity_errors { - // TODO - } - - mod schedule_build_errors { - // TODO errors other than system ambiguity errors - } -} From 6541956d89c3b6bdcf684ba00516484c6b043d7b Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Mon, 21 Nov 2022 09:48:07 -0800 Subject: [PATCH 27/64] fmt --- crates/bevy_ecs/src/schedule_v3/mod.rs | 92 +++++++++++---------- crates/bevy_ecs/src/schedule_v3/schedule.rs | 3 +- 2 files changed, 52 insertions(+), 43 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/mod.rs b/crates/bevy_ecs/src/schedule_v3/mod.rs index d06b86b95d7a7..e81c7390b66ca 100644 --- a/crates/bevy_ecs/src/schedule_v3/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/mod.rs @@ -140,7 +140,11 @@ mod tests { 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.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]); @@ -151,7 +155,11 @@ mod tests { // 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(3) + .before(TestSet::A) + .after(named_system), + ); schedule.add_system(make_function_system(4).after(TestSet::A)); schedule.run(&mut world); @@ -180,10 +188,10 @@ mod tests { 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( ( @@ -194,17 +202,17 @@ mod tests { ) .chain(), ); - + schedule.run(&mut world); assert_eq!(world.resource::().0, vec![0, 1, 2, 3]); } } - mod run_conditions { + mod conditions { use super::*; #[test] - fn system_with_run_condition() { + fn system_with_condition() { let mut world = World::default(); let mut schedule = Schedule::default(); @@ -224,7 +232,7 @@ mod tests { } #[test] - fn run_exclusive_system_with_run_condition() { + fn run_exclusive_system_with_condition() { let mut world = World::default(); let mut schedule = Schedule::default(); @@ -244,7 +252,7 @@ mod tests { } #[test] - fn multiple_run_criteria_on_system() { + fn multiple_conditions_on_system() { let mut world = World::default(); let mut schedule = Schedule::default(); @@ -260,7 +268,7 @@ mod tests { } #[test] - fn multiple_run_criteria_on_system_sets() { + fn multiple_conditions_on_system_sets() { let mut world = World::default(); let mut schedule = Schedule::default(); @@ -302,80 +310,80 @@ mod tests { 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(BuildError::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(BuildError::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(BuildError::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(BuildError::SystemTypeSetAmbiguity(_)))); - + // same goes for `ambiguous_with` let mut schedule = Schedule::new(); schedule.add_system(foo); @@ -386,17 +394,17 @@ mod tests { let result = schedule.initialize(&mut world); assert!(matches!(result, Err(BuildError::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() { @@ -404,49 +412,49 @@ mod tests { let mut schedule = Schedule::new(); schedule.configure_set(foo.into_system_set()); } - + #[test] fn hierarchy_conflict() { let mut world = World::new(); let mut schedule = Schedule::new(); - + // 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(BuildError::HierarchyConflict))); } - + #[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(BuildError::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.add_systems((res_ref, res_mut)); let result = schedule.initialize(&mut world); assert!(matches!(result, Err(BuildError::Ambiguity))); diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index e28394b13d150..94332595db2d0 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -164,7 +164,7 @@ impl Schedule { /// Initializes all uninitialized systems and conditions, rebuilds the executable schedule, /// and re-initializes executor data structures. - pub(crate) fn initialize(&mut self, world: &mut World) -> Result<(), BuildError> { + pub fn initialize(&mut self, world: &mut World) -> Result<(), BuildError> { if self.graph.changed { self.graph.initialize(world); self.graph.update_schedule(&mut self.executable)?; @@ -882,6 +882,7 @@ impl ScheduleMeta { } } +// methods for reporting errors impl ScheduleMeta { fn get_node_name(&self, id: &NodeId) -> Cow<'static, str> { match id { From 7e11b44dd968e9e2fa6ee6ecd8a437f3238cc23f Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Tue, 22 Nov 2022 13:10:34 -0800 Subject: [PATCH 28/64] builder pattern for `Schedule` methods --- crates/bevy_ecs/src/schedule_v3/migration.rs | 9 ++-- crates/bevy_ecs/src/schedule_v3/mod.rs | 51 ++++++++------------ crates/bevy_ecs/src/schedule_v3/schedule.rs | 18 ++++--- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/migration.rs b/crates/bevy_ecs/src/schedule_v3/migration.rs index 7c27e1815a7cb..345593c9ffa07 100644 --- a/crates/bevy_ecs/src/schedule_v3/migration.rs +++ b/crates/bevy_ecs/src/schedule_v3/migration.rs @@ -8,11 +8,14 @@ pub trait AppExt { /// /// **Note:** This will create the schedule if it does not already exist. fn set_default_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self; - /// Sets the [`Schedule`] that will be modified by default within the scope of `f` and calls it. - /// Afterwards, restores the default to its previous value. + /// 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 Self)) -> &mut Self; + fn edit_schedule( + &mut self, + label: impl ScheduleLabel, + f: impl FnMut(&mut Schedule), + ) -> &mut Self; } /// New "stageless" [`World`] methods. diff --git a/crates/bevy_ecs/src/schedule_v3/mod.rs b/crates/bevy_ecs/src/schedule_v3/mod.rs index e81c7390b66ca..3650fa58139af 100644 --- a/crates/bevy_ecs/src/schedule_v3/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/mod.rs @@ -26,16 +26,6 @@ mod tests { pub use crate::system::{Res, ResMut}; pub use crate::{prelude::World, system::Resource}; - #[allow(dead_code)] - #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] - enum TestSchedule { - A, - B, - C, - D, - X, - } - #[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)] enum TestSet { A, @@ -75,8 +65,6 @@ mod tests { } mod system_execution { - use std::sync::{Arc, Barrier}; - use super::*; #[test] @@ -107,24 +95,27 @@ mod tests { #[test] fn parallel_execution() { - let mut world = World::default(); - let mut schedule = Schedule::default(); - - let barrier = Arc::new(Barrier::new(3)); - - let barrier1 = barrier.clone(); - schedule.add_system(move || { - barrier1.wait(); - }); - let barrier2 = barrier.clone(); - schedule.add_system(move || { - barrier2.wait(); - }); - schedule.add_system(move || { - barrier.wait(); - }); - - schedule.run(&mut world); + // TODO: Miri + // use std::sync::{Arc, Barrier}; + + // let mut world = World::default(); + // let mut schedule = Schedule::default(); + + // let barrier = Arc::new(Barrier::new(3)); + + // let barrier1 = barrier.clone(); + // schedule.add_system(move || { + // barrier1.wait(); + // }); + // let barrier2 = barrier.clone(); + // schedule.add_system(move || { + // barrier2.wait(); + // }); + // schedule.add_system(move || { + // barrier.wait(); + // }); + + // schedule.run(&mut world); } } diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index 94332595db2d0..19dd48c38c30e 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -114,29 +114,34 @@ impl Schedule { } /// Add a system to the schedule. - pub fn add_system

(&mut self, system: impl IntoSystemConfig

) { + 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

) { + 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) { + 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) { + pub fn configure_sets(&mut self, sets: impl IntoSystemSetConfigs) -> &mut Self { self.graph.configure_sets(sets); + self } /// Sets 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) { + pub fn set_default_set(&mut self, set: impl SystemSet) -> &mut Self { self.graph.set_default_set(set); + self } /// Returns the schedule's current execution strategy. @@ -145,7 +150,7 @@ impl Schedule { } /// Sets the schedule's execution strategy. - pub fn set_executor_kind(&mut self, executor: ExecutorKind) { + 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()), @@ -154,6 +159,7 @@ impl Schedule { }; self.executor.init(&self.executable); } + self } /// Runs all systems in this schedule on the `world`, using its current execution strategy. From 6ee040a737ee3742ca5f5433e26a53b165225077 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Tue, 22 Nov 2022 13:38:07 -0800 Subject: [PATCH 29/64] miri --- crates/bevy_ecs/src/schedule_v3/mod.rs | 42 +++++++++++++------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/mod.rs b/crates/bevy_ecs/src/schedule_v3/mod.rs index 3650fa58139af..52077a50b3e90 100644 --- a/crates/bevy_ecs/src/schedule_v3/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/mod.rs @@ -94,28 +94,28 @@ mod tests { } #[test] + #[cfg(not(miri))] fn parallel_execution() { - // TODO: Miri - // use std::sync::{Arc, Barrier}; - - // let mut world = World::default(); - // let mut schedule = Schedule::default(); - - // let barrier = Arc::new(Barrier::new(3)); - - // let barrier1 = barrier.clone(); - // schedule.add_system(move || { - // barrier1.wait(); - // }); - // let barrier2 = barrier.clone(); - // schedule.add_system(move || { - // barrier2.wait(); - // }); - // schedule.add_system(move || { - // barrier.wait(); - // }); - - // schedule.run(&mut world); + use std::sync::{Arc, Barrier}; + + let mut world = World::default(); + let mut schedule = Schedule::default(); + + let barrier = Arc::new(Barrier::new(3)); + + let barrier1 = barrier.clone(); + schedule.add_system(move || { + barrier1.wait(); + }); + let barrier2 = barrier.clone(); + schedule.add_system(move || { + barrier2.wait(); + }); + schedule.add_system(move || { + barrier.wait(); + }); + + schedule.run(&mut world); } } From a0bbdae40dd5fcc36e890db7f9239dbd4b9fb035 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Tue, 22 Nov 2022 20:14:23 -0800 Subject: [PATCH 30/64] style + ticks --- .../bevy_ecs/src/schedule_v3/executor/mod.rs | 5 ++ .../schedule_v3/executor/multi_threaded.rs | 64 +++++++++---------- .../src/schedule_v3/executor/simple.rs | 45 ++++++------- .../schedule_v3/executor/single_threaded.rs | 47 +++++++------- crates/bevy_ecs/src/schedule_v3/schedule.rs | 11 +++- 5 files changed, 87 insertions(+), 85 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs index 85e37f832e6d0..9adf2df487d2c 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs @@ -88,3 +88,8 @@ pub(super) fn is_apply_system_buffers(system: &BoxedSystem) -> bool { // deref to use `System::type_id` instead of `Any::type_id` system.as_ref().type_id() == apply_system_buffers.type_id() } + +/// Returns the number of change ticks elapsed. +pub(super) fn ticks_since(system_tick: u32, world_tick: u32) -> u32 { + world_tick.wrapping_sub(system_tick) +} diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index 4f5c473933c71..f0ed2f34c9acb 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -10,7 +10,9 @@ use fixedbitset::FixedBitSet; use crate::{ archetype::ArchetypeComponentId, query::Access, - schedule_v3::{is_apply_system_buffers, ExecutorKind, SystemExecutor, SystemSchedule}, + schedule_v3::{ + is_apply_system_buffers, ticks_since, ExecutorKind, SystemExecutor, SystemSchedule, + }, world::World, }; @@ -94,11 +96,6 @@ impl SystemExecutor for MultiThreadedExecutor { } fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) { - // The start of schedule execution is the best time to do this. - world.check_change_ticks(); - - #[cfg(feature = "trace")] - let _schedule_span = info_span!("schedule").entered(); 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 @@ -253,15 +250,15 @@ impl MultiThreadedExecutor { self.active_access .extend(&system_meta.archetype_component_access); - self.ready_systems.set(system_index, false); - self.running_systems.insert(system_index); - if system_meta.is_send { scope.spawn(task); } else { self.local_thread_running = true; scope.spawn_on_scope(task); } + + self.ready_systems.set(system_index, false); + self.running_systems.insert(system_index); } fn spawn_exclusive_system_task<'scope>( @@ -280,12 +277,6 @@ impl MultiThreadedExecutor { let system_span = info_span!("system", name = &*system.name()); let sender = self.sender.clone(); - - self.ready_systems.set(system_index, false); - self.running_systems.insert(system_index); - self.local_thread_running = true; - self.exclusive_running = true; - if is_apply_system_buffers(system) { // TODO: avoid allocation let mut unapplied_systems = self.unapplied_systems.clone(); @@ -321,6 +312,11 @@ impl MultiThreadedExecutor { let task = task.instrument(task_span); scope.spawn_on_scope(task); } + + self.local_thread_running = true; + self.exclusive_running = true; + self.ready_systems.set(system_index, false); + self.running_systems.insert(system_index); } fn can_run(&mut self, system_index: usize, schedule: &SystemSchedule, world: &World) -> bool { @@ -372,8 +368,9 @@ impl MultiThreadedExecutor { // evaluate conditions let mut should_run = true; + let world_tick = world.read_change_tick(); - // evaluate set conditions in hierarchical order + // evaluate system set conditions in hierarchical order for set_idx in schedule.sets_of_systems[system_index].ones() { if self.completed_sets.contains(set_idx) { continue; @@ -385,9 +382,10 @@ impl MultiThreadedExecutor { let saved_tick = set_conditions .iter() .map(|condition| condition.get_last_change_tick()) - .min(); + .min_by_key(|&tick| ticks_since(tick, world_tick)); let set_conditions_met = set_conditions.iter_mut().all(|condition| { + condition.set_last_change_tick(saved_tick.unwrap()); #[cfg(feature = "trace")] let _condition_span = info_span!("condition", name = &*condition.name()).entered(); // SAFETY: access is compatible @@ -397,20 +395,19 @@ impl MultiThreadedExecutor { self.completed_sets.insert(set_idx); if !set_conditions_met { + // restore change ticks + for condition in set_conditions.iter_mut() { + condition.set_last_change_tick(saved_tick.unwrap()); + } + // mark all members as completed for sys_idx in schedule.systems_of_sets[set_idx].ones() { if !self.completed_systems.contains(sys_idx) { self.skip_system_and_signal_dependents(sys_idx); } } - self.completed_sets .union_with(&schedule.sets_of_sets[set_idx]); - - // restore condition change ticks - for condition in set_conditions.iter_mut() { - condition.set_last_change_tick(saved_tick.unwrap()); - } } should_run &= set_conditions_met; @@ -423,17 +420,16 @@ impl MultiThreadedExecutor { let system = schedule.systems[system_index].borrow(); // evaluate the system's conditions - let mut system_conditions = schedule.system_conditions[system_index].borrow_mut(); - for condition in system_conditions.iter_mut() { - condition.set_last_change_tick(system.get_last_change_tick()); - } - - let should_run = system_conditions.iter_mut().all(|condition| { - #[cfg(feature = "trace")] - let _condition_span = info_span!("condition", name = &*condition.name()).entered(); - // SAFETY: access is compatible - unsafe { condition.run_unsafe((), world) } - }); + should_run = schedule.system_conditions[system_index] + .borrow_mut() + .iter_mut() + .all(|condition| { + condition.set_last_change_tick(system.get_last_change_tick()); + #[cfg(feature = "trace")] + let _condition_span = info_span!("condition", name = &*condition.name()).entered(); + // SAFETY: access is compatible + unsafe { condition.run_unsafe((), world) } + }); if !should_run { self.skip_system_and_signal_dependents(system_index); diff --git a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs index 793169741afb9..67dcc1c42a255 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs @@ -3,7 +3,7 @@ use bevy_utils::tracing::info_span; use fixedbitset::FixedBitSet; use crate::{ - schedule_v3::{ExecutorKind, SystemExecutor, SystemSchedule}, + schedule_v3::{ticks_since, ExecutorKind, SystemExecutor, SystemSchedule}, world::World, }; @@ -30,11 +30,6 @@ impl SystemExecutor for SimpleExecutor { } fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) { - // The start of schedule execution is the best time to do this. - world.check_change_ticks(); - - #[cfg(feature = "trace")] - let _schedule_span = info_span!("schedule").entered(); for sys_idx in 0..schedule.systems.len() { if self.completed_systems.contains(sys_idx) { continue; @@ -47,8 +42,9 @@ impl SystemExecutor for SimpleExecutor { // evaluate conditions let mut should_run = true; + let world_tick = world.change_tick(); - // evaluate set conditions in hierarchical order + // evaluate system set conditions in hierarchical order for set_idx in schedule.sets_of_systems[sys_idx].ones() { if self.completed_sets.contains(set_idx) { continue; @@ -60,9 +56,10 @@ impl SystemExecutor for SimpleExecutor { let saved_tick = set_conditions .iter() .map(|condition| condition.get_last_change_tick()) - .min(); + .min_by_key(|&tick| ticks_since(tick, world_tick)); let set_conditions_met = set_conditions.iter_mut().all(|condition| { + condition.set_last_change_tick(saved_tick.unwrap()); #[cfg(feature = "trace")] let _condition_span = info_span!("condition", name = &*condition.name()).entered(); @@ -72,16 +69,16 @@ impl SystemExecutor for SimpleExecutor { self.completed_sets.insert(set_idx); if !set_conditions_met { + // restore condition change ticks + for condition in set_conditions.iter_mut() { + condition.set_last_change_tick(saved_tick.unwrap()); + } + // mark all members as completed self.completed_systems .union_with(&schedule.systems_of_sets[set_idx]); self.completed_sets .union_with(&schedule.sets_of_sets[set_idx]); - - // restore condition change ticks - for condition in set_conditions.iter_mut() { - condition.set_last_change_tick(saved_tick.unwrap()); - } } should_run &= set_conditions_met; @@ -94,21 +91,21 @@ impl SystemExecutor for SimpleExecutor { let system = schedule.systems[sys_idx].get_mut(); // evaluate the system's conditions - let system_conditions = schedule.system_conditions[sys_idx].get_mut(); - for condition in system_conditions.iter_mut() { - condition.set_last_change_tick(system.get_last_change_tick()); - } - - let should_run = system_conditions.iter_mut().all(|condition| { - #[cfg(feature = "trace")] - let _condition_span = info_span!("condition", name = &*condition.name()).entered(); - condition.run((), world) - }); + should_run = schedule.system_conditions[sys_idx] + .get_mut() + .iter_mut() + .all(|condition| { + condition.set_last_change_tick(system.get_last_change_tick()); + #[cfg(feature = "trace")] + let _condition_span = + info_span!("condition", name = &*condition.name()).entered(); + condition.run((), world) + }); #[cfg(feature = "trace")] should_run_span.exit(); - // mark system as completed regardless + // system has been skipped or will run self.completed_systems.insert(sys_idx); if !should_run { diff --git a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs index 5d2a610f6b7e7..170e85837fbfa 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs @@ -3,7 +3,9 @@ use bevy_utils::tracing::info_span; use fixedbitset::FixedBitSet; use crate::{ - schedule_v3::{is_apply_system_buffers, ExecutorKind, SystemExecutor, SystemSchedule}, + schedule_v3::{ + is_apply_system_buffers, ticks_since, ExecutorKind, SystemExecutor, SystemSchedule, + }, world::World, }; @@ -33,11 +35,6 @@ impl SystemExecutor for SingleThreadedExecutor { } fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) { - // The start of schedule execution is the best time to do this. - world.check_change_ticks(); - - #[cfg(feature = "trace")] - let _schedule_span = info_span!("schedule").entered(); for sys_idx in 0..schedule.systems.len() { if self.completed_systems.contains(sys_idx) { continue; @@ -50,8 +47,9 @@ impl SystemExecutor for SingleThreadedExecutor { // evaluate conditions let mut should_run = true; + let world_tick = world.change_tick(); - // evaluate set conditions in hierarchical order + // evaluate system set conditions in hierarchical order for set_idx in schedule.sets_of_systems[sys_idx].ones() { if self.completed_sets.contains(set_idx) { continue; @@ -63,9 +61,10 @@ impl SystemExecutor for SingleThreadedExecutor { let saved_tick = set_conditions .iter() .map(|condition| condition.get_last_change_tick()) - .min(); + .min_by_key(|&tick| ticks_since(tick, world_tick)); let set_conditions_met = set_conditions.iter_mut().all(|condition| { + condition.set_last_change_tick(saved_tick.unwrap()); #[cfg(feature = "trace")] let _condition_span = info_span!("condition", name = &*condition.name()).entered(); @@ -75,16 +74,16 @@ impl SystemExecutor for SingleThreadedExecutor { self.completed_sets.insert(set_idx); if !set_conditions_met { + // restore change ticks + for condition in set_conditions.iter_mut() { + condition.set_last_change_tick(saved_tick.unwrap()); + } + // mark all members as completed self.completed_systems .union_with(&schedule.systems_of_sets[set_idx]); self.completed_sets .union_with(&schedule.sets_of_sets[set_idx]); - - // restore condition change ticks - for condition in set_conditions.iter_mut() { - condition.set_last_change_tick(saved_tick.unwrap()); - } } should_run &= set_conditions_met; @@ -97,21 +96,21 @@ impl SystemExecutor for SingleThreadedExecutor { let system = schedule.systems[sys_idx].get_mut(); // evaluate the system's conditions - let system_conditions = schedule.system_conditions[sys_idx].get_mut(); - for condition in system_conditions.iter_mut() { - condition.set_last_change_tick(system.get_last_change_tick()); - } - - let should_run = system_conditions.iter_mut().all(|condition| { - #[cfg(feature = "trace")] - let _condition_span = info_span!("condition", name = &*condition.name()).entered(); - condition.run((), world) - }); + should_run = schedule.system_conditions[sys_idx] + .get_mut() + .iter_mut() + .all(|condition| { + condition.set_last_change_tick(system.get_last_change_tick()); + #[cfg(feature = "trace")] + let _condition_span = + info_span!("condition", name = &*condition.name()).entered(); + condition.run((), world) + }); #[cfg(feature = "trace")] should_run_span.exit(); - // mark system as completed regardless + // system has been skipped or will run self.completed_systems.insert(sys_idx); if !should_run { diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index 19dd48c38c30e..ac7b58f0156b2 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -4,6 +4,8 @@ use std::{ result::Result, }; +#[cfg(feature = "trace")] +use bevy_utils::tracing::info_span; use bevy_utils::{ petgraph::{algo::tarjan_scc, prelude::*}, thiserror::Error, @@ -75,15 +77,14 @@ impl Schedules { /// times since the previous pass. pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!("check stored schedule ticks").entered(); + 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 _span = - bevy_utils::tracing::info_span!("check schedule ticks", name = &name).entered(); + let _one_span = info_span!("check schedule ticks", name = &name).entered(); schedule.check_change_ticks(change_tick); } } @@ -165,6 +166,10 @@ impl Schedule { /// Runs all systems in this schedule on the `world`, using its current execution strategy. pub fn run(&mut self, world: &mut World) { self.initialize(world).unwrap(); + world.check_change_ticks(); + // TODO: get a label on this span + #[cfg(feature = "trace")] + let _span = info_span!("schedule").entered(); self.executor.run(&mut self.executable, world); } From 23f22500386e161d1111f818be5abe07dd9a9492 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Tue, 22 Nov 2022 20:38:50 -0800 Subject: [PATCH 31/64] rename fields for clarity --- crates/bevy_ecs/src/schedule_v3/executor/mod.rs | 8 ++++---- .../src/schedule_v3/executor/multi_threaded.rs | 4 ++-- crates/bevy_ecs/src/schedule_v3/executor/simple.rs | 4 ++-- .../src/schedule_v3/executor/single_threaded.rs | 4 ++-- crates/bevy_ecs/src/schedule_v3/schedule.rs | 12 ++++++------ 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs index 9adf2df487d2c..c7858ffe5acff 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs @@ -47,8 +47,8 @@ pub(super) struct SystemSchedule { pub(super) set_ids: Vec, pub(super) system_deps: Vec<(usize, Vec)>, pub(super) sets_of_systems: Vec, - pub(super) sets_of_sets: Vec, - pub(super) systems_of_sets: Vec, + pub(super) sets_in_sets: Vec, + pub(super) systems_in_sets: Vec, } impl SystemSchedule { @@ -61,8 +61,8 @@ impl SystemSchedule { set_ids: Vec::new(), system_deps: Vec::new(), sets_of_systems: Vec::new(), - sets_of_sets: Vec::new(), - systems_of_sets: Vec::new(), + sets_in_sets: Vec::new(), + systems_in_sets: Vec::new(), } } } diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index f0ed2f34c9acb..466ddce855501 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -401,13 +401,13 @@ impl MultiThreadedExecutor { } // mark all members as completed - for sys_idx in schedule.systems_of_sets[set_idx].ones() { + for sys_idx in schedule.systems_in_sets[set_idx].ones() { if !self.completed_systems.contains(sys_idx) { self.skip_system_and_signal_dependents(sys_idx); } } self.completed_sets - .union_with(&schedule.sets_of_sets[set_idx]); + .union_with(&schedule.sets_in_sets[set_idx]); } should_run &= set_conditions_met; diff --git a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs index 67dcc1c42a255..02d4d00c46d35 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs @@ -76,9 +76,9 @@ impl SystemExecutor for SimpleExecutor { // mark all members as completed self.completed_systems - .union_with(&schedule.systems_of_sets[set_idx]); + .union_with(&schedule.systems_in_sets[set_idx]); self.completed_sets - .union_with(&schedule.sets_of_sets[set_idx]); + .union_with(&schedule.sets_in_sets[set_idx]); } should_run &= set_conditions_met; diff --git a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs index 170e85837fbfa..19c26e3c3d002 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs @@ -81,9 +81,9 @@ impl SystemExecutor for SingleThreadedExecutor { // mark all members as completed self.completed_systems - .union_with(&schedule.systems_of_sets[set_idx]); + .union_with(&schedule.systems_in_sets[set_idx]); self.completed_sets - .union_with(&schedule.sets_of_sets[set_idx]); + .union_with(&schedule.sets_in_sets[set_idx]); } should_run &= set_conditions_met; diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index ac7b58f0156b2..b747ab23f8141 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -801,18 +801,18 @@ impl ScheduleMeta { // get the rows and columns of the hierarchy graph's reachability matrix // (needed to we can evaluate conditions in the correct order) - let mut sets_of_sets = vec![FixedBitSet::with_capacity(set_count); set_count]; + let mut sets_in_sets = vec![FixedBitSet::with_capacity(set_count); set_count]; for (i, &row) in hg_set_idxs.iter().enumerate() { - let bitset = &mut sets_of_sets[i]; + let bitset = &mut sets_in_sets[i]; for (idx, &col) in hg_set_idxs.iter().enumerate().skip(i) { let is_descendant = result.reachable[index(row, col, node_count)]; bitset.set(idx, is_descendant); } } - let mut systems_of_sets = vec![FixedBitSet::with_capacity(sys_count); set_count]; + 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_of_sets[i]; + 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 = result.reachable[index(row, col, node_count)]; @@ -842,8 +842,8 @@ impl ScheduleMeta { set_ids: hg_set_ids, system_deps, sets_of_systems, - sets_of_sets, - systems_of_sets, + sets_in_sets, + systems_in_sets, } } From 24c1945f5ec0154686a467c4685d721d5234422d Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 4 Dec 2022 21:17:31 -0800 Subject: [PATCH 32/64] respond to feedback pt1 --- crates/bevy_ecs/src/schedule_v3/condition.rs | 4 +- crates/bevy_ecs/src/schedule_v3/config.rs | 36 +-- .../bevy_ecs/src/schedule_v3/executor/mod.rs | 15 +- .../schedule_v3/executor/multi_threaded.rs | 254 +++++++++--------- .../src/schedule_v3/executor/simple.rs | 92 +++---- .../schedule_v3/executor/single_threaded.rs | 100 +++---- crates/bevy_ecs/src/schedule_v3/migration.rs | 4 + crates/bevy_ecs/src/schedule_v3/schedule.rs | 212 +++++++-------- 8 files changed, 328 insertions(+), 389 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/condition.rs b/crates/bevy_ecs/src/schedule_v3/condition.rs index aeae8bdf51085..35acb510da9f0 100644 --- a/crates/bevy_ecs/src/schedule_v3/condition.rs +++ b/crates/bevy_ecs/src/schedule_v3/condition.rs @@ -1,4 +1,4 @@ -pub use helper::*; +pub use common_conditions::*; use crate::system::BoxedSystem; @@ -29,7 +29,7 @@ mod sealed { } } -pub mod helper { +mod common_conditions { use crate::schedule_v3::{State, Statelike}; use crate::system::{Res, Resource}; diff --git a/crates/bevy_ecs/src/schedule_v3/config.rs b/crates/bevy_ecs/src/schedule_v3/config.rs index 283f9935f7a10..4bc05a25eeaec 100644 --- a/crates/bevy_ecs/src/schedule_v3/config.rs +++ b/crates/bevy_ecs/src/schedule_v3/config.rs @@ -164,22 +164,21 @@ impl IntoSystemSetConfig for SystemSetConfig { fn in_set(mut self, set: impl SystemSet) -> Self { assert!(!set.is_system_type(), "invalid use of system type set"); - self.graph_info.sets.insert(set.dyn_clone()); + self.graph_info.sets.insert(Box::new(set)); self } fn before(mut self, set: impl IntoSystemSet) -> Self { - self.graph_info.dependencies.insert(( - DependencyEdgeKind::Before, - set.into_system_set().dyn_clone(), - )); + self.graph_info + .dependencies + .insert((DependencyEdgeKind::Before, Box::new(set.into_system_set()))); self } fn after(mut self, set: impl IntoSystemSet) -> Self { self.graph_info .dependencies - .insert((DependencyEdgeKind::After, set.into_system_set().dyn_clone())); + .insert((DependencyEdgeKind::After, Box::new(set.into_system_set()))); self } @@ -189,15 +188,16 @@ impl IntoSystemSetConfig for SystemSetConfig { } fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { - let set = set.into_system_set(); match &mut self.graph_info.ambiguous_with { detection @ Ambiguity::Check => { let mut ambiguous_with = HashSet::new(); - ambiguous_with.insert(set.dyn_clone()); + let boxed: Box = Box::new(set.into_system_set()); + ambiguous_with.insert(boxed); *detection = Ambiguity::IgnoreWithSet(ambiguous_with); } Ambiguity::IgnoreWithSet(ambiguous_with) => { - ambiguous_with.insert(set.dyn_clone()); + let boxed: Box = Box::new(set.into_system_set()); + ambiguous_with.insert(boxed); } Ambiguity::IgnoreAll => (), } @@ -308,22 +308,21 @@ impl IntoSystemConfig<()> for SystemConfig { fn in_set(mut self, set: impl SystemSet) -> Self { assert!(!set.is_system_type(), "invalid use of system type set"); - self.graph_info.sets.insert(set.dyn_clone()); + self.graph_info.sets.insert(Box::new(set)); self } fn before(mut self, set: impl IntoSystemSet) -> Self { - self.graph_info.dependencies.insert(( - DependencyEdgeKind::Before, - set.into_system_set().dyn_clone(), - )); + self.graph_info + .dependencies + .insert((DependencyEdgeKind::Before, Box::new(set.into_system_set()))); self } fn after(mut self, set: impl IntoSystemSet) -> Self { self.graph_info .dependencies - .insert((DependencyEdgeKind::After, set.into_system_set().dyn_clone())); + .insert((DependencyEdgeKind::After, Box::new(set.into_system_set()))); self } @@ -333,15 +332,16 @@ impl IntoSystemConfig<()> for SystemConfig { } fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { - let set = set.into_system_set(); match &mut self.graph_info.ambiguous_with { detection @ Ambiguity::Check => { let mut ambiguous_with = HashSet::new(); - ambiguous_with.insert(set.dyn_clone()); + let boxed: Box = Box::new(set.into_system_set()); + ambiguous_with.insert(boxed); *detection = Ambiguity::IgnoreWithSet(ambiguous_with); } Ambiguity::IgnoreWithSet(ambiguous_with) => { - ambiguous_with.insert(set.dyn_clone()); + let boxed: Box = Box::new(set.into_system_set()); + ambiguous_with.insert(boxed); } Ambiguity::IgnoreAll => (), } diff --git a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs index c7858ffe5acff..dfbc4fde85363 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs @@ -33,8 +33,8 @@ pub enum ExecutorKind { /// Like [`SingleThreaded`](ExecutorKind::SingleThreaded) but calls [`apply_buffers`](crate::system::System::apply_buffers) /// immediately after running each system. Simple, - #[default] /// Runs the schedule using a thread pool. Non-conflicting systems can run in parallel. + #[default] MultiThreaded, } @@ -45,9 +45,9 @@ pub(super) struct SystemSchedule { pub(super) set_conditions: Vec>>, pub(super) system_ids: Vec, pub(super) set_ids: Vec, - pub(super) system_deps: Vec<(usize, Vec)>, + pub(super) system_dependencies: Vec, + pub(super) system_dependents: Vec>, pub(super) sets_of_systems: Vec, - pub(super) sets_in_sets: Vec, pub(super) systems_in_sets: Vec, } @@ -59,9 +59,9 @@ impl SystemSchedule { set_conditions: Vec::new(), system_ids: Vec::new(), set_ids: Vec::new(), - system_deps: Vec::new(), + system_dependencies: Vec::new(), + system_dependents: Vec::new(), sets_of_systems: Vec::new(), - sets_in_sets: Vec::new(), systems_in_sets: Vec::new(), } } @@ -88,8 +88,3 @@ pub(super) fn is_apply_system_buffers(system: &BoxedSystem) -> bool { // deref to use `System::type_id` instead of `Any::type_id` system.as_ref().type_id() == apply_system_buffers.type_id() } - -/// Returns the number of change ticks elapsed. -pub(super) fn ticks_since(system_tick: u32, world_tick: u32) -> u32 { - world_tick.wrapping_sub(system_tick) -} diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index 466ddce855501..fc34f6d9c8359 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -10,17 +10,15 @@ use fixedbitset::FixedBitSet; use crate::{ archetype::ArchetypeComponentId, query::Access, - schedule_v3::{ - is_apply_system_buffers, ticks_since, ExecutorKind, SystemExecutor, SystemSchedule, - }, + schedule_v3::{is_apply_system_buffers, ExecutorKind, SystemExecutor, SystemSchedule}, world::World, }; /// Per-system data used by the [`MultiThreadedExecutor`]. -struct SystemTaskMetadata { +// Copied here because it can't be read from the system when it's running. +struct SystemTaskMeta { /// Indices of the systems that directly depend on the system. dependents: Vec, - // These values are cached because we can't read them from the system while it's running. /// The `ArchetypeComponentId` access of the system. archetype_component_access: Access, /// Is `true` if the system does not access `!Send` data. @@ -31,13 +29,13 @@ struct SystemTaskMetadata { /// Runs the schedule using a thread pool. Non-conflicting systems can run in parallel. pub struct MultiThreadedExecutor { - /// Metadata for scheduling and running system tasks. - system_task_metadata: Vec, /// Sends system completion events. sender: Sender, /// Receives system completion events. receiver: Receiver, - /// The number of dependencies the system has that have not completed. + /// Metadata for scheduling and running system tasks. + system_task_meta: Vec, + /// The number of dependencies each system has that have not completed. dependencies_remaining: Vec, /// Union of the accesses of all currently running systems. active_access: Access, @@ -45,14 +43,16 @@ pub struct MultiThreadedExecutor { local_thread_running: bool, /// Returns `true` if an exclusive system is running. exclusive_running: bool, - /// System sets that have been skipped or had their conditions evaluated. - completed_sets: FixedBitSet, - /// Systems that have run or been skipped. - completed_systems: FixedBitSet, + /// System sets whose conditions have been evaluated. + evaluated_sets: FixedBitSet, /// Systems that have no remaining dependencies and are waiting to run. ready_systems: FixedBitSet, - /// Systems that are currently running. + /// 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, } @@ -73,26 +73,25 @@ impl SystemExecutor for MultiThreadedExecutor { let sys_count = schedule.system_ids.len(); let set_count = schedule.set_ids.len(); - self.completed_sets = FixedBitSet::with_capacity(set_count); - self.completed_systems = FixedBitSet::with_capacity(sys_count); + self.evaluated_sets = FixedBitSet::with_capacity(set_count); self.ready_systems = 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.dependencies_remaining = Vec::with_capacity(sys_count); - self.system_task_metadata = Vec::with_capacity(sys_count); - + self.system_task_meta = Vec::with_capacity(sys_count); for index in 0..sys_count { - let (num_dependencies, dependents) = schedule.system_deps[index].clone(); let system = schedule.systems[index].borrow(); - self.dependencies_remaining.push(num_dependencies); - self.system_task_metadata.push(SystemTaskMetadata { - dependents, + self.system_task_meta.push(SystemTaskMeta { + dependents: schedule.system_dependents[index].clone(), + archetype_component_access: default(), is_send: system.is_send(), is_exclusive: system.is_exclusive(), - archetype_component_access: default(), }); } + + self.dependencies_remaining = Vec::with_capacity(sys_count); } fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) { @@ -100,23 +99,29 @@ impl SystemExecutor for MultiThreadedExecutor { // the executor itself is a `Send` future so that it can run // alongside systems that claim the local thread let executor = async { - // systems with zero dependencies - for (index, dependencies) in self.dependencies_remaining.iter().enumerate() { + // reset counts + self.dependencies_remaining.clear(); + self.dependencies_remaining + .extend_from_slice(&schedule.system_dependencies); + + for (system_index, dependencies) in + self.dependencies_remaining.iter_mut().enumerate() + { if *dependencies == 0 { - self.ready_systems.insert(index); + self.ready_systems.insert(system_index); } } - // spare bitset to avoid repeated allocations - let mut ready = FixedBitSet::with_capacity(self.ready_systems.len()); + // using spare bitset to avoid repeated allocations + let mut ready_systems = FixedBitSet::with_capacity(self.ready_systems.len()); // main loop let world = SyncUnsafeCell::from_mut(world); while self.completed_systems.count_ones(..) != self.completed_systems.len() { if !self.exclusive_running { - ready.clear(); - ready.union_with(&self.ready_systems); - self.spawn_system_tasks(&ready, scope, schedule, world); + ready_systems.clear(); + ready_systems.union_with(&self.ready_systems); + self.spawn_system_tasks(&ready_systems, scope, schedule, world); } if !self.running_systems.is_clear() { @@ -145,7 +150,7 @@ impl SystemExecutor for MultiThreadedExecutor { debug_assert!(self.running_systems.is_clear()); debug_assert!(self.unapplied_systems.is_clear()); self.active_access.clear(); - self.completed_sets.clear(); + self.evaluated_sets.clear(); self.completed_systems.clear(); }; @@ -162,35 +167,31 @@ impl MultiThreadedExecutor { pub fn new() -> Self { let (sender, receiver) = async_channel::unbounded(); Self { - system_task_metadata: Vec::new(), sender, receiver, + system_task_meta: Vec::new(), dependencies_remaining: Vec::new(), active_access: default(), local_thread_running: false, exclusive_running: false, - completed_sets: FixedBitSet::new(), - completed_systems: FixedBitSet::new(), + evaluated_sets: FixedBitSet::new(), ready_systems: FixedBitSet::new(), running_systems: FixedBitSet::new(), + skipped_systems: FixedBitSet::new(), + completed_systems: FixedBitSet::new(), unapplied_systems: FixedBitSet::new(), } } fn spawn_system_tasks<'scope>( &mut self, - ready: &FixedBitSet, + ready_systems: &FixedBitSet, scope: &Scope<'_, 'scope, ()>, schedule: &'scope SystemSchedule, cell: &'scope SyncUnsafeCell, ) { - for system_index in ready.ones() { - // systems can be skipped within this loop - if self.completed_systems.contains(system_index) { - continue; - } - - // SAFETY: no exclusive system running + for system_index in ready_systems.ones() { + // SAFETY: no exclusive system is running let world = unsafe { &*cell.get() }; if !self.can_run(system_index, schedule, world) { // NOTE: exclusive systems with ambiguities are susceptible to @@ -199,18 +200,26 @@ impl MultiThreadedExecutor { // if that becomes an issue, `break;` if exclusive system continue; } + + // system is either going to run or be skipped + self.ready_systems.set(system_index, false); + if !self.should_run(system_index, schedule, world) { + self.skip_system_and_signal_dependents(system_index); continue; } - if !self.system_task_metadata[system_index].is_exclusive { - self.spawn_system_task(scope, system_index, schedule, world); - } else { - // SAFETY: no other system running + // system is starting + self.running_systems.insert(system_index); + + if self.system_task_meta[system_index].is_exclusive { + // SAFETY: `can_run` confirmed no other systems are running let world = unsafe { &mut *cell.get() }; self.spawn_exclusive_system_task(scope, system_index, schedule, world); break; } + + self.spawn_system_task(scope, system_index, schedule, world); } } @@ -246,7 +255,7 @@ impl MultiThreadedExecutor { #[cfg(feature = "trace")] let task = task.instrument(task_span); - let system_meta = &self.system_task_metadata[system_index]; + let system_meta = &self.system_task_meta[system_index]; self.active_access .extend(&system_meta.archetype_component_access); @@ -256,9 +265,6 @@ impl MultiThreadedExecutor { self.local_thread_running = true; scope.spawn_on_scope(task); } - - self.ready_systems.set(system_index, false); - self.running_systems.insert(system_index); } fn spawn_exclusive_system_task<'scope>( @@ -315,8 +321,6 @@ impl MultiThreadedExecutor { self.local_thread_running = true; self.exclusive_running = true; - self.ready_systems.set(system_index, false); - self.running_systems.insert(system_index); } fn can_run(&mut self, system_index: usize, schedule: &SystemSchedule, world: &World) -> bool { @@ -325,34 +329,57 @@ impl MultiThreadedExecutor { #[cfg(feature = "trace")] let _span = info_span!("check_access", name = &*name).entered(); - let system_meta = &mut self.system_task_metadata[system_index]; - if self.local_thread_running && !system_meta.is_send { - // only one thread can access thread-local resources + // TODO: an earlier out if archetypes did not change + let system_meta = &mut self.system_task_meta[system_index]; + + if system_meta.is_exclusive && !self.running_systems.is_clear() { return false; } - let mut system = schedule.systems[system_index].borrow_mut(); - system.update_archetype_component_access(world); + if !system_meta.is_send && self.local_thread_running { + return false; + } - // TODO: avoid allocation - system_meta.archetype_component_access = system.archetype_component_access().clone(); - let mut total_access = system.archetype_component_access().clone(); + for set_idx in schedule.sets_of_systems[system_index].difference(&self.evaluated_sets) { + for condition in schedule.set_conditions[set_idx].borrow_mut().iter_mut() { + condition.update_archetype_component_access(world); + if !condition + .archetype_component_access() + .is_compatible(&self.active_access) + { + return false; + } + } + } - let mut system_conditions = schedule.system_conditions[system_index].borrow_mut(); - for condition in system_conditions.iter_mut() { + for condition in schedule.system_conditions[system_index] + .borrow_mut() + .iter_mut() + { condition.update_archetype_component_access(world); - total_access.extend(condition.archetype_component_access()); + if !condition + .archetype_component_access() + .is_compatible(&self.active_access) + { + return false; + } } - for set_idx in schedule.sets_of_systems[system_index].difference(&self.completed_sets) { - let mut set_conditions = schedule.set_conditions[set_idx].borrow_mut(); - for condition in set_conditions.iter_mut() { - condition.update_archetype_component_access(world); - total_access.extend(condition.archetype_component_access()); + if !self.skipped_systems.contains(system_index) { + let mut system = schedule.systems[system_index].borrow_mut(); + system.update_archetype_component_access(world); + if !system + .archetype_component_access() + .is_compatible(&self.active_access) + { + return false; } + + // TODO: avoid allocation by keeping count of readers + system_meta.archetype_component_access = system.archetype_component_access().clone(); } - total_access.is_compatible(&self.active_access) + true } fn should_run( @@ -366,85 +393,61 @@ impl MultiThreadedExecutor { #[cfg(feature = "trace")] let _span = info_span!("check_conditions", name = &*name).entered(); - // evaluate conditions - let mut should_run = true; - let world_tick = world.read_change_tick(); - - // evaluate system set conditions in hierarchical order + let mut should_run = !self.completed_systems.contains(system_index); for set_idx in schedule.sets_of_systems[system_index].ones() { - if self.completed_sets.contains(set_idx) { + if self.evaluated_sets.contains(set_idx) { continue; } - let mut set_conditions = schedule.set_conditions[set_idx].borrow_mut(); - - // if any condition fails, we need to restore their change ticks - let saved_tick = set_conditions - .iter() - .map(|condition| condition.get_last_change_tick()) - .min_by_key(|&tick| ticks_since(tick, world_tick)); - - let set_conditions_met = set_conditions.iter_mut().all(|condition| { - condition.set_last_change_tick(saved_tick.unwrap()); - #[cfg(feature = "trace")] - let _condition_span = info_span!("condition", name = &*condition.name()).entered(); - // SAFETY: access is compatible - unsafe { condition.run_unsafe((), world) } - }); - - self.completed_sets.insert(set_idx); + // evaluate system set's conditions + let set_conditions_met = schedule.set_conditions[set_idx] + .borrow_mut() + .iter_mut() + .map(|condition| { + #[cfg(feature = "trace")] + let _condition_span = + info_span!("condition", name = &*condition.name()).entered(); + // SAFETY: access is compatible + unsafe { condition.run_unsafe((), world) } + }) + .fold(true, |acc, res| acc && res); if !set_conditions_met { - // restore change ticks - for condition in set_conditions.iter_mut() { - condition.set_last_change_tick(saved_tick.unwrap()); - } - - // mark all members as completed - for sys_idx in schedule.systems_in_sets[set_idx].ones() { - if !self.completed_systems.contains(sys_idx) { - self.skip_system_and_signal_dependents(sys_idx); - } - } - self.completed_sets - .union_with(&schedule.sets_in_sets[set_idx]); + self.skipped_systems + .union_with(&schedule.systems_in_sets[set_idx]); } should_run &= set_conditions_met; + self.evaluated_sets.insert(set_idx); } - if !should_run { - return false; - } - - let system = schedule.systems[system_index].borrow(); - - // evaluate the system's conditions - should_run = schedule.system_conditions[system_index] + // evaluate system's conditions + let system_conditions_met = schedule.system_conditions[system_index] .borrow_mut() .iter_mut() - .all(|condition| { - condition.set_last_change_tick(system.get_last_change_tick()); + .map(|condition| { #[cfg(feature = "trace")] let _condition_span = info_span!("condition", name = &*condition.name()).entered(); // SAFETY: access is compatible unsafe { condition.run_unsafe((), world) } - }); + }) + .fold(true, |acc, res| acc && res); - if !should_run { - self.skip_system_and_signal_dependents(system_index); - return false; + if !system_conditions_met { + self.skipped_systems.insert(system_index); } - true + should_run &= system_conditions_met; + + should_run } fn finish_system_and_signal_dependents(&mut self, system_index: usize) { - if !self.system_task_metadata[system_index].is_send { + if !self.system_task_meta[system_index].is_send { self.local_thread_running = false; } - if self.system_task_metadata[system_index].is_exclusive { + if self.system_task_meta[system_index].is_exclusive { self.exclusive_running = false; } @@ -455,7 +458,6 @@ impl MultiThreadedExecutor { } fn skip_system_and_signal_dependents(&mut self, system_index: usize) { - self.ready_systems.set(system_index, false); self.completed_systems.insert(system_index); self.signal_dependents(system_index); } @@ -463,7 +465,7 @@ impl MultiThreadedExecutor { 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 { + for &dep_idx in &self.system_task_meta[system_index].dependents { let dependencies = &mut self.dependencies_remaining[dep_idx]; *dependencies -= 1; if *dependencies == 0 && !self.completed_systems.contains(dep_idx) { @@ -475,7 +477,7 @@ impl MultiThreadedExecutor { 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]; + let system_meta = &self.system_task_meta[index]; self.active_access .extend(&system_meta.archetype_component_access); } diff --git a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs index 02d4d00c46d35..325730b4c4fe5 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs @@ -3,7 +3,7 @@ use bevy_utils::tracing::info_span; use fixedbitset::FixedBitSet; use crate::{ - schedule_v3::{ticks_since, ExecutorKind, SystemExecutor, SystemSchedule}, + schedule_v3::{ExecutorKind, SystemExecutor, SystemSchedule}, world::World, }; @@ -11,8 +11,8 @@ use crate::{ /// [`apply_buffers`](crate::system::System::apply_buffers) immediately after running each system. #[derive(Default)] pub struct SimpleExecutor { - /// Systems sets whose conditions have either been evaluated or skipped. - completed_sets: FixedBitSet, + /// Systems sets whose conditions have been evaluated. + evaluated_sets: FixedBitSet, /// Systems that have run or been skipped. completed_systems: FixedBitSet, } @@ -25,93 +25,69 @@ impl SystemExecutor for SimpleExecutor { fn init(&mut self, schedule: &SystemSchedule) { let sys_count = schedule.system_ids.len(); let set_count = schedule.set_ids.len(); - self.completed_sets = FixedBitSet::with_capacity(set_count); + 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 sys_idx in 0..schedule.systems.len() { - if self.completed_systems.contains(sys_idx) { - continue; - } - + for system_index in 0..schedule.systems.len() { #[cfg(feature = "trace")] - let name = schedule.systems[sys_idx].get_mut().name(); + let name = schedule.systems[system_index].get_mut().name(); #[cfg(feature = "trace")] let should_run_span = info_span!("check_conditions", name = &*name).entered(); - // evaluate conditions - let mut should_run = true; - let world_tick = world.change_tick(); - - // evaluate system set conditions in hierarchical order - for set_idx in schedule.sets_of_systems[sys_idx].ones() { - if self.completed_sets.contains(set_idx) { + 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; } - let set_conditions = schedule.set_conditions[set_idx].get_mut(); - - // if any condition fails, we need to restore their change ticks - let saved_tick = set_conditions - .iter() - .map(|condition| condition.get_last_change_tick()) - .min_by_key(|&tick| ticks_since(tick, world_tick)); - - let set_conditions_met = set_conditions.iter_mut().all(|condition| { - condition.set_last_change_tick(saved_tick.unwrap()); - #[cfg(feature = "trace")] - let _condition_span = - info_span!("condition", name = &*condition.name()).entered(); - condition.run((), world) - }); - - self.completed_sets.insert(set_idx); + // evaluate system set's conditions + let set_conditions_met = schedule.set_conditions[set_idx] + .borrow_mut() + .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); if !set_conditions_met { - // restore condition change ticks - for condition in set_conditions.iter_mut() { - condition.set_last_change_tick(saved_tick.unwrap()); - } - - // mark all members as completed self.completed_systems .union_with(&schedule.systems_in_sets[set_idx]); - self.completed_sets - .union_with(&schedule.sets_in_sets[set_idx]); } should_run &= set_conditions_met; + self.evaluated_sets.insert(set_idx); } - if !should_run { - continue; - } - - let system = schedule.systems[sys_idx].get_mut(); - - // evaluate the system's conditions - should_run = schedule.system_conditions[sys_idx] - .get_mut() + // evaluate system's conditions + let system_conditions_met = schedule.system_conditions[system_index] + .borrow_mut() .iter_mut() - .all(|condition| { - condition.set_last_change_tick(system.get_last_change_tick()); + .map(|condition| { #[cfg(feature = "trace")] let _condition_span = info_span!("condition", name = &*condition.name()).entered(); condition.run((), world) - }); + }) + .fold(true, |acc, res| acc && res); + + should_run &= system_conditions_met; #[cfg(feature = "trace")] should_run_span.exit(); - // system has been skipped or will run - self.completed_systems.insert(sys_idx); + // system has either been skipped or will run + self.completed_systems.insert(system_index); if !should_run { continue; } + let system = schedule.systems[system_index].get_mut(); #[cfg(feature = "trace")] let system_span = info_span!("system", name = &*name).entered(); system.run((), world); @@ -123,7 +99,7 @@ impl SystemExecutor for SimpleExecutor { system.apply_buffers(world); } - self.completed_sets.clear(); + self.evaluated_sets.clear(); self.completed_systems.clear(); } } @@ -131,7 +107,7 @@ impl SystemExecutor for SimpleExecutor { impl SimpleExecutor { pub const fn new() -> Self { Self { - completed_sets: FixedBitSet::new(), + evaluated_sets: FixedBitSet::new(), completed_systems: FixedBitSet::new(), } } diff --git a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs index 19c26e3c3d002..78127057d209c 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs @@ -3,17 +3,15 @@ use bevy_utils::tracing::info_span; use fixedbitset::FixedBitSet; use crate::{ - schedule_v3::{ - is_apply_system_buffers, ticks_since, ExecutorKind, SystemExecutor, SystemSchedule, - }, + schedule_v3::{is_apply_system_buffers, ExecutorKind, SystemExecutor, SystemSchedule}, world::World, }; /// Runs the schedule using a single thread. #[derive(Default)] pub struct SingleThreadedExecutor { - /// System sets whose conditions have either been evaluated or skipped. - completed_sets: FixedBitSet, + /// 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. @@ -29,94 +27,70 @@ impl SystemExecutor for SingleThreadedExecutor { // pre-allocate space let sys_count = schedule.system_ids.len(); let set_count = schedule.set_ids.len(); - self.completed_sets = FixedBitSet::with_capacity(set_count); + 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 sys_idx in 0..schedule.systems.len() { - if self.completed_systems.contains(sys_idx) { - continue; - } - + for system_index in 0..schedule.systems.len() { #[cfg(feature = "trace")] - let name = schedule.systems[sys_idx].get_mut().name(); + let name = schedule.systems[system_index].get_mut().name(); #[cfg(feature = "trace")] let should_run_span = info_span!("check_conditions", name = &*name).entered(); - // evaluate conditions - let mut should_run = true; - let world_tick = world.change_tick(); - - // evaluate system set conditions in hierarchical order - for set_idx in schedule.sets_of_systems[sys_idx].ones() { - if self.completed_sets.contains(set_idx) { + 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; } - let set_conditions = schedule.set_conditions[set_idx].get_mut(); - - // if any condition fails, we need to restore their change ticks - let saved_tick = set_conditions - .iter() - .map(|condition| condition.get_last_change_tick()) - .min_by_key(|&tick| ticks_since(tick, world_tick)); - - let set_conditions_met = set_conditions.iter_mut().all(|condition| { - condition.set_last_change_tick(saved_tick.unwrap()); - #[cfg(feature = "trace")] - let _condition_span = - info_span!("condition", name = &*condition.name()).entered(); - condition.run((), world) - }); - - self.completed_sets.insert(set_idx); + // evaluate system set's conditions + let set_conditions_met = schedule.set_conditions[set_idx] + .borrow_mut() + .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); if !set_conditions_met { - // restore change ticks - for condition in set_conditions.iter_mut() { - condition.set_last_change_tick(saved_tick.unwrap()); - } - - // mark all members as completed self.completed_systems .union_with(&schedule.systems_in_sets[set_idx]); - self.completed_sets - .union_with(&schedule.sets_in_sets[set_idx]); } should_run &= set_conditions_met; + self.evaluated_sets.insert(set_idx); } - if !should_run { - continue; - } - - let system = schedule.systems[sys_idx].get_mut(); - - // evaluate the system's conditions - should_run = schedule.system_conditions[sys_idx] - .get_mut() + // evaluate system's conditions + let system_conditions_met = schedule.system_conditions[system_index] + .borrow_mut() .iter_mut() - .all(|condition| { - condition.set_last_change_tick(system.get_last_change_tick()); + .map(|condition| { #[cfg(feature = "trace")] let _condition_span = info_span!("condition", name = &*condition.name()).entered(); condition.run((), world) - }); + }) + .fold(true, |acc, res| acc && res); + + should_run &= system_conditions_met; #[cfg(feature = "trace")] should_run_span.exit(); - // system has been skipped or will run - self.completed_systems.insert(sys_idx); + // system has either been skipped or will run + self.completed_systems.insert(system_index); if !should_run { continue; } + let system = schedule.systems[system_index].get_mut(); if is_apply_system_buffers(system) { #[cfg(feature = "trace")] let system_span = info_span!("system", name = &*name).entered(); @@ -129,12 +103,12 @@ impl SystemExecutor for SingleThreadedExecutor { system.run((), world); #[cfg(feature = "trace")] system_span.exit(); - self.unapplied_systems.set(sys_idx, true); + self.unapplied_systems.set(system_index, true); } } self.apply_system_buffers(schedule, world); - self.completed_sets.clear(); + self.evaluated_sets.clear(); self.completed_systems.clear(); } } @@ -142,15 +116,15 @@ impl SystemExecutor for SingleThreadedExecutor { impl SingleThreadedExecutor { pub const fn new() -> Self { Self { - completed_sets: FixedBitSet::new(), + 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 sys_idx in self.unapplied_systems.ones() { - let system = schedule.systems[sys_idx].get_mut(); + for system_index in self.unapplied_systems.ones() { + let system = schedule.systems[system_index].get_mut(); #[cfg(feature = "trace")] let _apply_buffers_span = info_span!("apply_buffers", name = &*system.name()).entered(); system.apply_buffers(world); diff --git a/crates/bevy_ecs/src/schedule_v3/migration.rs b/crates/bevy_ecs/src/schedule_v3/migration.rs index 345593c9ffa07..8e95b28174fb0 100644 --- a/crates/bevy_ecs/src/schedule_v3/migration.rs +++ b/crates/bevy_ecs/src/schedule_v3/migration.rs @@ -16,6 +16,10 @@ pub trait AppExt { 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; } /// New "stageless" [`World`] methods. diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index b747ab23f8141..38cd6d89c4ed8 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -1,5 +1,5 @@ use std::{ - borrow::Cow, + cell::RefCell, fmt::{Debug, Write}, result::Result, }; @@ -42,7 +42,7 @@ impl Schedules { /// 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 = label.dyn_clone(); + let label: Box = Box::new(label); if self.inner.contains_key(&label) { warn!("schedule with label {:?} already exists", label); } @@ -51,30 +51,25 @@ impl Schedules { /// Removes the `label` entry from the map, returning its schedule if one existed. pub fn remove(&mut self, label: &dyn ScheduleLabel) -> Option { - let label = label.dyn_clone(); - if !self.inner.contains_key(&label) { + if !self.inner.contains_key(label) { warn!("schedule with label {:?} not found", label); } - self.inner.remove(&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> { - let label = label.dyn_clone(); - self.inner.get(&label) + 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> { - let label = label.dyn_clone(); - self.inner.get_mut(&label) + self.inner.get_mut(label) } - /// Iterates all system change ticks and clamps any older than [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE). + /// 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. - /// - /// **Note:** Does nothing if the [`World`] counter has not been incremented at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD) - /// times since the previous pass. pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { #[cfg(feature = "trace")] let _all_span = info_span!("check stored schedule ticks").entered(); @@ -93,9 +88,10 @@ impl Schedules { /// A collection of systems, and the metadata and executor needed to run them /// in a certain order under certain conditions. pub struct Schedule { - graph: ScheduleMeta, + graph: ScheduleGraph, executable: SystemSchedule, executor: Box, + executor_initialized: bool, } impl Default for Schedule { @@ -108,9 +104,10 @@ impl Schedule { /// Constructs an empty `Schedule`. pub fn new() -> Self { Self { - graph: ScheduleMeta::new(), + graph: ScheduleGraph::new(), executable: SystemSchedule::new(), executor: Box::new(MultiThreadedExecutor::new()), + executor_initialized: false, } } @@ -158,39 +155,42 @@ impl Schedule { ExecutorKind::SingleThreaded => Box::new(SingleThreadedExecutor::new()), ExecutorKind::MultiThreaded => Box::new(MultiThreadedExecutor::new()), }; - self.executor.init(&self.executable); + 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) { - self.initialize(world).unwrap(); world.check_change_ticks(); - // TODO: get a label on this span + self.initialize(world).unwrap(); + // TODO: label #[cfg(feature = "trace")] let _span = info_span!("schedule").entered(); self.executor.run(&mut self.executable, world); } - /// Initializes all uninitialized systems and conditions, rebuilds the executable schedule, - /// and re-initializes executor data structures. + /// 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<(), BuildError> { if self.graph.changed { self.graph.initialize(world); self.graph.update_schedule(&mut self.executable)?; - self.executor.init(&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 all component change ticks and clamps any older than [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE). + /// 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. - /// - /// **Note:** Does nothing if the [`World`] counter has not been incremented at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD) - /// times since the previous pass. pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { for system in &mut self.executable.systems { system.borrow_mut().check_change_tick(change_tick); @@ -228,7 +228,7 @@ impl Dag { } } -/// Metadata for a [`SystemSet`], stored in a [`ScheduleMeta`]. +/// Metadata for a [`SystemSet`], stored in a [`ScheduleGraph`]. struct SystemSetMeta { set: BoxedSystemSet, /// `true` if this system set was modified with `configure_set` @@ -243,20 +243,21 @@ impl SystemSetMeta { } } - pub fn name(&self) -> Cow<'static, str> { - format!("{:?}", &self.set).into() + pub fn name(&self) -> String { + format!("{:?}", &self.set) } pub fn is_system_type(&self) -> bool { self.set.is_system_type() } + // TODO: rename pub fn dyn_clone(&self) -> BoxedSystemSet { self.set.dyn_clone() } } -/// Uninitialized systems associated with a graph node, stored in a [`ScheduleMeta`]. +/// Uninitialized systems associated with a graph node, stored in a [`ScheduleGraph`]. enum UninitNode { System(BoxedSystem, Vec), SystemSet(Vec), @@ -264,7 +265,7 @@ enum UninitNode { /// Metadata for a [`Schedule`]. #[derive(Default)] -struct ScheduleMeta { +struct ScheduleGraph { system_set_ids: HashMap, system_sets: HashMap, systems: HashMap, @@ -284,7 +285,7 @@ struct ScheduleMeta { uninit: Vec<(NodeId, UninitNode)>, } -impl ScheduleMeta { +impl ScheduleGraph { pub fn new() -> Self { Self { system_set_ids: HashMap::new(), @@ -312,20 +313,24 @@ impl ScheduleMeta { fn set_default_set(&mut self, set: impl SystemSet) { assert!(!set.is_system_type(), "invalid use of system type set"); - self.default_set = Some(set.dyn_clone()); + self.default_set = Some(Box::new(set)); } fn add_systems

(&mut self, systems: impl IntoSystemConfigs

) { let SystemConfigs { systems, chained } = systems.into_configs(); - let mut iter = systems - .into_iter() - .map(|system| self.add_system_inner(system).unwrap()); - + let mut system_iter = systems.into_iter(); if chained { - let ids = iter.collect::>(); - self.chain(ids); + 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 { - while iter.next().is_some() {} + for system in system_iter { + self.add_system_inner(system).unwrap(); + } } } @@ -363,15 +368,19 @@ impl ScheduleMeta { fn configure_sets(&mut self, sets: impl IntoSystemSetConfigs) { let SystemSetConfigs { sets, chained } = sets.into_configs(); - let mut iter = sets - .into_iter() - .map(|set| self.configure_set_inner(set).unwrap()); - + let mut set_iter = sets.into_iter(); if chained { - let ids = iter.collect::>(); - self.chain(ids); + 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 { - while iter.next().is_some() {} + for set in set_iter { + self.configure_set_inner(set).unwrap(); + } } } @@ -421,12 +430,6 @@ impl ScheduleMeta { id } - fn chain(&mut self, nodes: Vec) { - for pair in nodes.windows(2) { - self.dependency.graph.add_edge(pair[0], pair[1], ()); - } - } - fn check_sets(&mut self, id: &NodeId, graph_info: &GraphInfo) -> Result<(), BuildError> { for set in graph_info.sets.iter() { match self.system_set_ids.get(set) { @@ -547,7 +550,7 @@ impl ScheduleMeta { } } - fn build_dependency_graph(&mut self) -> Result<(), BuildError> { + fn build_schedule(&mut self) -> Result { // check hierarchy for cycles let hier_scc = tarjan_scc(&self.hierarchy.graph); if self.contains_cycles(&hier_scc) { @@ -739,13 +742,7 @@ impl ScheduleMeta { return Err(BuildError::Ambiguity); } - Ok(()) - } - - fn build_schedule(&mut self) -> SystemSchedule { - let sys_count = self.systems.len(); - let node_count = self.systems.len() + self.system_sets.len(); - + // build the schedule let dg_system_ids = self.dependency_flattened.topsort.clone(); let dg_system_idx_map = dg_system_ids .iter() @@ -754,26 +751,6 @@ impl ScheduleMeta { .map(|(i, id)| (id, i)) .collect::>(); - // 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_deps = 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_deps.push((num_dependencies, dependents)); - } - let hg_systems = self .hierarchy .topsort @@ -796,26 +773,40 @@ impl ScheduleMeta { }) .unzip(); + let sys_count = self.systems.len(); let set_count = hg_set_ids.len(); - let result = check_graph(&self.hierarchy.graph, &self.hierarchy.topsort); + let node_count = self.systems.len() + self.system_sets.len(); - // get the rows and columns of the hierarchy graph's reachability matrix - // (needed to we can evaluate conditions in the correct order) - let mut sets_in_sets = vec![FixedBitSet::with_capacity(set_count); set_count]; - for (i, &row) in hg_set_idxs.iter().enumerate() { - let bitset = &mut sets_in_sets[i]; - for (idx, &col) in hg_set_idxs.iter().enumerate().skip(i) { - let is_descendant = result.reachable[index(row, col, node_count)]; - bitset.set(idx, is_descendant); - } + // 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 = result.reachable[index(row, col, node_count)]; + let is_descendant = hier_results.reachable[index(row, col, node_count)]; bitset.set(idx, is_descendant); } } @@ -829,27 +820,25 @@ impl ScheduleMeta { .enumerate() .take_while(|&(_idx, &row)| row < col) { - let is_ancestor = result.reachable[index(row, col, node_count)]; + let is_ancestor = hier_results.reachable[index(row, col, node_count)]; bitset.set(idx, is_ancestor); } } - SystemSchedule { + 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_deps, + system_dependencies, + system_dependents, sets_of_systems, - sets_in_sets, systems_in_sets, - } + }) } fn update_schedule(&mut self, schedule: &mut SystemSchedule) -> Result<(), BuildError> { - use std::cell::RefCell; - if !self.uninit.is_empty() { return Err(BuildError::Uninitialized); } @@ -873,8 +862,7 @@ impl ScheduleMeta { self.conditions.insert(id, conditions.into_inner()); } - self.build_dependency_graph()?; - *schedule = self.build_schedule(); + *schedule = self.build_schedule()?; // move systems into new schedule for &id in &schedule.system_ids { @@ -894,10 +882,10 @@ impl ScheduleMeta { } // methods for reporting errors -impl ScheduleMeta { - fn get_node_name(&self, id: &NodeId) -> Cow<'static, str> { +impl ScheduleGraph { + fn get_node_name(&self, id: &NodeId) -> String { match id { - NodeId::System(_) => self.systems[id].name(), + NodeId::System(_) => self.systems[id].name().to_string(), NodeId::Set(_) => self.system_sets[id].name(), } } @@ -1006,22 +994,22 @@ impl ScheduleMeta { #[derive(Error, Debug)] #[non_exhaustive] pub enum BuildError { - #[error("`{0:?}` contains itself")] + #[error("`{0:?}` contains itself.")] HierarchyLoop(BoxedSystemSet), - #[error("system set hierarchy contains cycle(s)")] + #[error("System set hierarchy contains cycle(s).")] HierarchyCycle, - #[error("system set hierarchy contains conflicting relationships")] + #[error("System set hierarchy contains conflicting relationships.")] HierarchyConflict, - #[error("`{0:?}` depends on itself")] + #[error("`{0:?}` depends on itself.")] DependencyLoop(BoxedSystemSet), - #[error("dependencies contain cycle(s)")] + #[error("System dependencies contain cycle(s).")] DependencyCycle, - #[error("`{0:?}` and `{1:?}` can have a hierarchical OR dependent relationship, not both")] + #[error("`{0:?}` and `{1:?}` have both `in_set` and `before`-`after` relationships (possibly transitive). This combination is unsolvable as a system cannot run before or after a set it belongs to.")] CrossDependency(String, String), - #[error("ambiguous relationship with `{0:?}`, multiple instances of this system exist")] + #[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), - #[error("systems with conflicting access have indeterminate execution order")] + #[error("Systems with conflicting access have indeterminate run order.")] Ambiguity, - #[error("schedule not initialized")] + #[error("Schedule is not initialized.")] Uninitialized, } From 3af4d288fb96484eb4fce06f9d719c8a5a33c4a5 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Fri, 13 Jan 2023 16:05:30 -0800 Subject: [PATCH 33/64] docs --- crates/bevy_utils/src/syncunsafecell.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/bevy_utils/src/syncunsafecell.rs b/crates/bevy_utils/src/syncunsafecell.rs index 1a97bf677c57c..b610764302a35 100644 --- a/crates/bevy_utils/src/syncunsafecell.rs +++ b/crates/bevy_utils/src/syncunsafecell.rs @@ -1,8 +1,12 @@ +//! A reimplementation of the currently unstable [`std::cell::SyncUnsafeCell`] +//! +//! [`std::cell::SyncUnsafeCell`]: https://doc.rust-lang.org/nightly/std/cell/struct.SyncUnsafeCell.html + pub use core::cell::UnsafeCell; /// [`UnsafeCell`], but [`Sync`]. /// -/// See [tracking issue](https://github.com/rust-lang/rust/issues/95439) for upcoming [`core`] impl, +/// See [tracking issue](https://github.com/rust-lang/rust/issues/95439) for upcoming native impl, /// which should replace this one entirely (except `from_mut`). /// /// This is just an `UnsafeCell`, except it implements `Sync` From 4869bed3212f069ea1e3d61eea27e0e1796f6f84 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Fri, 13 Jan 2023 16:08:13 -0800 Subject: [PATCH 34/64] update to match system param API changes --- crates/bevy_ecs/src/schedule_v3/condition.rs | 7 +++---- crates/bevy_ecs/src/world/mod.rs | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/condition.rs b/crates/bevy_ecs/src/schedule_v3/condition.rs index 35acb510da9f0..f48a776d227de 100644 --- a/crates/bevy_ecs/src/schedule_v3/condition.rs +++ b/crates/bevy_ecs/src/schedule_v3/condition.rs @@ -7,14 +7,14 @@ 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::ReadOnlySystemParamFetch) parameters. +/// 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, ReadOnlySystemParamFetch, SystemParam, SystemParamFunction, + IntoSystem, IsFunctionSystem, ReadOnlySystemParam, SystemParam, SystemParamFunction, }; pub trait Condition: IntoSystem<(), bool, Params> {} @@ -22,8 +22,7 @@ mod sealed { impl Condition<(IsFunctionSystem, Params, Marker)> for F where F: SystemParamFunction<(), bool, Params, Marker> + Send + Sync + 'static, - Params: SystemParam + 'static, - Params::Fetch: ReadOnlySystemParamFetch, + Params: SystemParam + ReadOnlySystemParam + 'static, Marker: 'static, { } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 9d4d4c442fc3b..d9cfcfe68195f 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -65,7 +65,6 @@ pub struct World { pub(crate) change_tick: AtomicU32, pub(crate) last_change_tick: u32, pub(crate) last_check_tick: u32, - main_thread_validator: MainThreadValidator, } impl Default for World { From a2ef27b59b1cb6b60efcf2bc3d46046d6aca51d7 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Fri, 13 Jan 2023 16:32:52 -0800 Subject: [PATCH 35/64] beef with clippy --- .../schedule_v3/executor/multi_threaded.rs | 49 ++++++++++--------- .../src/schedule_v3/executor/simple.rs | 43 ++++++++-------- .../schedule_v3/executor/single_threaded.rs | 45 +++++++++-------- crates/bevy_ecs/src/schedule_v3/schedule.rs | 2 +- 4 files changed, 74 insertions(+), 65 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index fc34f6d9c8359..2a46c6766925c 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -10,7 +10,9 @@ use fixedbitset::FixedBitSet; use crate::{ archetype::ArchetypeComponentId, query::Access, - schedule_v3::{is_apply_system_buffers, ExecutorKind, SystemExecutor, SystemSchedule}, + schedule_v3::{ + is_apply_system_buffers, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule, + }, world::World, }; @@ -400,17 +402,10 @@ impl MultiThreadedExecutor { } // evaluate system set's conditions - let set_conditions_met = schedule.set_conditions[set_idx] - .borrow_mut() - .iter_mut() - .map(|condition| { - #[cfg(feature = "trace")] - let _condition_span = - info_span!("condition", name = &*condition.name()).entered(); - // SAFETY: access is compatible - unsafe { condition.run_unsafe((), world) } - }) - .fold(true, |acc, res| acc && res); + let set_conditions_met = evaluate_and_fold_conditions( + schedule.set_conditions[set_idx].borrow_mut().as_mut(), + world, + ); if !set_conditions_met { self.skipped_systems @@ -422,16 +417,12 @@ impl MultiThreadedExecutor { } // evaluate system's conditions - let system_conditions_met = schedule.system_conditions[system_index] - .borrow_mut() - .iter_mut() - .map(|condition| { - #[cfg(feature = "trace")] - let _condition_span = info_span!("condition", name = &*condition.name()).entered(); - // SAFETY: access is compatible - unsafe { condition.run_unsafe((), world) } - }) - .fold(true, |acc, res| acc && res); + let system_conditions_met = evaluate_and_fold_conditions( + schedule.system_conditions[system_index] + .borrow_mut() + .as_mut(), + world, + ); if !system_conditions_met { self.skipped_systems.insert(system_index); @@ -498,3 +489,17 @@ impl MultiThreadedExecutor { 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 index 325730b4c4fe5..ac24989f79d98 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs @@ -3,7 +3,7 @@ use bevy_utils::tracing::info_span; use fixedbitset::FixedBitSet; use crate::{ - schedule_v3::{ExecutorKind, SystemExecutor, SystemSchedule}, + schedule_v3::{BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule}, world::World, }; @@ -43,16 +43,10 @@ impl SystemExecutor for SimpleExecutor { } // evaluate system set's conditions - let set_conditions_met = schedule.set_conditions[set_idx] - .borrow_mut() - .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); + let set_conditions_met = evaluate_and_fold_conditions( + schedule.set_conditions[set_idx].get_mut().as_mut(), + world, + ); if !set_conditions_met { self.completed_systems @@ -64,16 +58,10 @@ impl SystemExecutor for SimpleExecutor { } // evaluate system's conditions - let system_conditions_met = schedule.system_conditions[system_index] - .borrow_mut() - .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); + let system_conditions_met = evaluate_and_fold_conditions( + schedule.system_conditions[system_index].get_mut().as_mut(), + world, + ); should_run &= system_conditions_met; @@ -112,3 +100,16 @@ impl SimpleExecutor { } } } + +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 index 78127057d209c..b0804b13ddd85 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs @@ -3,7 +3,9 @@ use bevy_utils::tracing::info_span; use fixedbitset::FixedBitSet; use crate::{ - schedule_v3::{is_apply_system_buffers, ExecutorKind, SystemExecutor, SystemSchedule}, + schedule_v3::{ + is_apply_system_buffers, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule, + }, world::World, }; @@ -46,16 +48,10 @@ impl SystemExecutor for SingleThreadedExecutor { } // evaluate system set's conditions - let set_conditions_met = schedule.set_conditions[set_idx] - .borrow_mut() - .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); + let set_conditions_met = evaluate_and_fold_conditions( + schedule.set_conditions[set_idx].get_mut().as_mut(), + world, + ); if !set_conditions_met { self.completed_systems @@ -67,16 +63,10 @@ impl SystemExecutor for SingleThreadedExecutor { } // evaluate system's conditions - let system_conditions_met = schedule.system_conditions[system_index] - .borrow_mut() - .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); + let system_conditions_met = evaluate_and_fold_conditions( + schedule.system_conditions[system_index].get_mut().as_mut(), + world, + ); should_run &= system_conditions_met; @@ -133,3 +123,16 @@ impl SingleThreadedExecutor { 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/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index 38cd6d89c4ed8..f5cd5ea6b3e05 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -582,7 +582,7 @@ impl ScheduleGraph { { let name_a = self.get_node_name(&a); let name_b = self.get_node_name(&b); - return Err(BuildError::CrossDependency(name_a.into(), name_b.into())); + return Err(BuildError::CrossDependency(name_a, name_b)); } } From 10227fd80e14b845388893b3a6cd53d53a4cf8bb Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Fri, 13 Jan 2023 20:30:39 -0800 Subject: [PATCH 36/64] reorganize --- .../schedule_v3/executor/multi_threaded.rs | 202 +++++++++--------- 1 file changed, 101 insertions(+), 101 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index 2a46c6766925c..07b550f272c20 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -225,106 +225,6 @@ impl MultiThreadedExecutor { } } - fn spawn_system_task<'scope>( - &mut self, - scope: &Scope<'_, 'scope, ()>, - system_index: usize, - schedule: &'scope SystemSchedule, - world: &'scope World, - ) { - // SAFETY: system was not already running - let system = unsafe { &mut *schedule.systems[system_index].as_ptr() }; - - #[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_meta[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); - } - } - - fn spawn_exclusive_system_task<'scope>( - &mut self, - scope: &Scope<'_, 'scope, ()>, - system_index: usize, - schedule: &'scope SystemSchedule, - world: &'scope mut World, - ) { - // SAFETY: system was not already running - let system = unsafe { &mut *schedule.systems[system_index].as_ptr() }; - - #[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(); - Self::apply_system_buffers(&mut unapplied_systems, schedule, 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.local_thread_running = true; - self.exclusive_running = true; - } - fn can_run(&mut self, system_index: usize, schedule: &SystemSchedule, world: &World) -> bool { #[cfg(feature = "trace")] let name = schedule.systems[system_index].borrow().name(); @@ -395,7 +295,7 @@ impl MultiThreadedExecutor { #[cfg(feature = "trace")] let _span = info_span!("check_conditions", name = &*name).entered(); - let mut should_run = !self.completed_systems.contains(system_index); + let mut should_run = !self.skipped_systems.contains(system_index); for set_idx in schedule.sets_of_systems[system_index].ones() { if self.evaluated_sets.contains(set_idx) { continue; @@ -433,6 +333,106 @@ impl MultiThreadedExecutor { should_run } + fn spawn_system_task<'scope>( + &mut self, + scope: &Scope<'_, 'scope, ()>, + system_index: usize, + schedule: &'scope SystemSchedule, + world: &'scope World, + ) { + // SAFETY: system was not already running + let system = unsafe { &mut *schedule.systems[system_index].as_ptr() }; + + #[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_meta[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); + } + } + + fn spawn_exclusive_system_task<'scope>( + &mut self, + scope: &Scope<'_, 'scope, ()>, + system_index: usize, + schedule: &'scope SystemSchedule, + world: &'scope mut World, + ) { + // SAFETY: system was not already running + let system = unsafe { &mut *schedule.systems[system_index].as_ptr() }; + + #[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(); + Self::apply_system_buffers(&mut unapplied_systems, schedule, 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.local_thread_running = true; + self.exclusive_running = true; + } + fn finish_system_and_signal_dependents(&mut self, system_index: usize) { if !self.system_task_meta[system_index].is_send { self.local_thread_running = false; From 528f9ddef71f1ab6084a7fdb3fe5d606da9ef0c9 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Fri, 13 Jan 2023 20:30:31 -0800 Subject: [PATCH 37/64] respond to feedback pt2 --- crates/bevy_ecs/macros/src/lib.rs | 4 +- crates/bevy_ecs/src/schedule_v3/config.rs | 57 +++++++++---------- .../bevy_ecs/src/schedule_v3/graph_utils.rs | 8 +-- crates/bevy_ecs/src/schedule_v3/schedule.rs | 17 ++---- crates/bevy_macro_utils/src/lib.rs | 2 +- 5 files changed, 41 insertions(+), 47 deletions(-) diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 9f95c63496fe9..07d01a8c870ab 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -5,7 +5,7 @@ mod fetch; use crate::fetch::derive_world_query_impl; use bevy_macro_utils::{ - derive_label, derive_old_style_label, derive_set, get_named_struct_fields, BevyManifest, + derive_boxed_label, derive_label, derive_set, get_named_struct_fields, BevyManifest, }; use proc_macro::TokenStream; use proc_macro2::Span; @@ -578,7 +578,7 @@ pub fn derive_schedule_label(input: TokenStream) -> TokenStream { trait_path .segments .push(format_ident!("ScheduleLabel").into()); - derive_old_style_label(input, &trait_path) + derive_boxed_label(input, &trait_path) } /// Derive macro generating an impl of the trait `SystemSet`. diff --git a/crates/bevy_ecs/src/schedule_v3/config.rs b/crates/bevy_ecs/src/schedule_v3/config.rs index 4bc05a25eeaec..5b80af4a7ee3d 100644 --- a/crates/bevy_ecs/src/schedule_v3/config.rs +++ b/crates/bevy_ecs/src/schedule_v3/config.rs @@ -1,5 +1,4 @@ use bevy_utils::prelude::default; -use bevy_utils::HashSet; use crate::{ schedule_v3::{ @@ -28,8 +27,8 @@ pub(super) fn new_set_unchecked(set: BoxedSystemSet) -> SystemSetConfig { SystemSetConfig { set, graph_info: GraphInfo { - sets: HashSet::new(), - dependencies: HashSet::new(), + sets: Vec::new(), + dependencies: Vec::new(), ambiguous_with: default(), }, conditions: Vec::new(), @@ -50,7 +49,7 @@ fn new_system(system: BoxedSystem) -> SystemConfig { system, graph_info: GraphInfo { sets, - dependencies: HashSet::new(), + dependencies: Vec::new(), ambiguous_with: default(), }, conditions: Vec::new(), @@ -99,31 +98,31 @@ where S: SystemSet + sealed::IntoSystemSetConfig, { fn into_config(self) -> SystemSetConfig { - new_set(self.dyn_clone()) + new_set(Box::new(self)) } fn in_set(self, set: impl SystemSet) -> SystemSetConfig { - new_set(self.dyn_clone()).in_set(set) + new_set(Box::new(self)).in_set(set) } fn before(self, set: impl IntoSystemSet) -> SystemSetConfig { - new_set(self.dyn_clone()).before(set) + new_set(Box::new(self)).before(set) } fn after(self, set: impl IntoSystemSet) -> SystemSetConfig { - new_set(self.dyn_clone()).after(set) + new_set(Box::new(self)).after(set) } fn run_if

(self, condition: impl Condition

) -> SystemSetConfig { - new_set(self.dyn_clone()).run_if(condition) + new_set(Box::new(self)).run_if(condition) } fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemSetConfig { - new_set(self.dyn_clone()).ambiguous_with(set) + new_set(Box::new(self)).ambiguous_with(set) } fn ambiguous_with_all(self) -> SystemSetConfig { - new_set(self.dyn_clone()).ambiguous_with_all() + new_set(Box::new(self)).ambiguous_with_all() } } @@ -164,21 +163,21 @@ impl IntoSystemSetConfig for SystemSetConfig { fn in_set(mut self, set: impl SystemSet) -> Self { assert!(!set.is_system_type(), "invalid use of system type set"); - self.graph_info.sets.insert(Box::new(set)); + self.graph_info.sets.push(Box::new(set)); self } fn before(mut self, set: impl IntoSystemSet) -> Self { self.graph_info .dependencies - .insert((DependencyEdgeKind::Before, Box::new(set.into_system_set()))); + .push((DependencyEdgeKind::Before, Box::new(set.into_system_set()))); self } fn after(mut self, set: impl IntoSystemSet) -> Self { self.graph_info .dependencies - .insert((DependencyEdgeKind::After, Box::new(set.into_system_set()))); + .push((DependencyEdgeKind::After, Box::new(set.into_system_set()))); self } @@ -190,14 +189,14 @@ impl IntoSystemSetConfig for SystemSetConfig { fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { match &mut self.graph_info.ambiguous_with { detection @ Ambiguity::Check => { - let mut ambiguous_with = HashSet::new(); + let mut ambiguous_with = Vec::new(); let boxed: Box = Box::new(set.into_system_set()); - ambiguous_with.insert(boxed); + ambiguous_with.push(boxed); *detection = Ambiguity::IgnoreWithSet(ambiguous_with); } Ambiguity::IgnoreWithSet(ambiguous_with) => { let boxed: Box = Box::new(set.into_system_set()); - ambiguous_with.insert(boxed); + ambiguous_with.push(boxed); } Ambiguity::IgnoreAll => (), } @@ -308,21 +307,21 @@ impl IntoSystemConfig<()> for SystemConfig { fn in_set(mut self, set: impl SystemSet) -> Self { assert!(!set.is_system_type(), "invalid use of system type set"); - self.graph_info.sets.insert(Box::new(set)); + self.graph_info.sets.push(Box::new(set)); self } fn before(mut self, set: impl IntoSystemSet) -> Self { self.graph_info .dependencies - .insert((DependencyEdgeKind::Before, Box::new(set.into_system_set()))); + .push((DependencyEdgeKind::Before, Box::new(set.into_system_set()))); self } fn after(mut self, set: impl IntoSystemSet) -> Self { self.graph_info .dependencies - .insert((DependencyEdgeKind::After, Box::new(set.into_system_set()))); + .push((DependencyEdgeKind::After, Box::new(set.into_system_set()))); self } @@ -334,14 +333,14 @@ impl IntoSystemConfig<()> for SystemConfig { fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { match &mut self.graph_info.ambiguous_with { detection @ Ambiguity::Check => { - let mut ambiguous_with = HashSet::new(); + let mut ambiguous_with = Vec::new(); let boxed: Box = Box::new(set.into_system_set()); - ambiguous_with.insert(boxed); + ambiguous_with.push(boxed); *detection = Ambiguity::IgnoreWithSet(ambiguous_with); } Ambiguity::IgnoreWithSet(ambiguous_with) => { let boxed: Box = Box::new(set.into_system_set()); - ambiguous_with.insert(boxed); + ambiguous_with.push(boxed); } Ambiguity::IgnoreAll => (), } @@ -428,7 +427,7 @@ impl IntoSystemConfigs<()> for SystemConfigs { fn in_set(mut self, set: impl SystemSet) -> Self { assert!(!set.is_system_type(), "invalid use of system type set"); for config in &mut self.systems { - config.graph_info.sets.insert(set.dyn_clone()); + config.graph_info.sets.push(set.dyn_clone()); } self @@ -440,7 +439,7 @@ impl IntoSystemConfigs<()> for SystemConfigs { config .graph_info .dependencies - .insert((DependencyEdgeKind::Before, set.dyn_clone())); + .push((DependencyEdgeKind::Before, set.dyn_clone())); } self @@ -452,7 +451,7 @@ impl IntoSystemConfigs<()> for SystemConfigs { config .graph_info .dependencies - .insert((DependencyEdgeKind::After, set.dyn_clone())); + .push((DependencyEdgeKind::After, set.dyn_clone())); } self @@ -511,7 +510,7 @@ impl IntoSystemSetConfigs for SystemSetConfigs { fn in_set(mut self, set: impl SystemSet) -> Self { assert!(!set.is_system_type(), "invalid use of system type set"); for config in &mut self.sets { - config.graph_info.sets.insert(set.dyn_clone()); + config.graph_info.sets.push(set.dyn_clone()); } self @@ -523,7 +522,7 @@ impl IntoSystemSetConfigs for SystemSetConfigs { config .graph_info .dependencies - .insert((DependencyEdgeKind::Before, set.dyn_clone())); + .push((DependencyEdgeKind::Before, set.dyn_clone())); } self @@ -535,7 +534,7 @@ impl IntoSystemSetConfigs for SystemSetConfigs { config .graph_info .dependencies - .insert((DependencyEdgeKind::After, set.dyn_clone())); + .push((DependencyEdgeKind::After, set.dyn_clone())); } self diff --git a/crates/bevy_ecs/src/schedule_v3/graph_utils.rs b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs index 312b2af990ed0..c9ea8da0b949a 100644 --- a/crates/bevy_ecs/src/schedule_v3/graph_utils.rs +++ b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs @@ -41,16 +41,16 @@ pub(crate) enum DependencyEdgeKind { pub(crate) enum Ambiguity { #[default] Check, - /// Ignore warnings with systems in any of these system sets. - IgnoreWithSet(HashSet), + /// 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: HashSet, - pub(crate) dependencies: HashSet<(DependencyEdgeKind, BoxedSystemSet)>, + pub(crate) sets: Vec, + pub(crate) dependencies: Vec<(DependencyEdgeKind, BoxedSystemSet)>, pub(crate) ambiguous_with: Ambiguity, } diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index f5cd5ea6b3e05..ba5c22824846c 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -250,11 +250,6 @@ impl SystemSetMeta { pub fn is_system_type(&self) -> bool { self.set.is_system_type() } - - // TODO: rename - pub fn dyn_clone(&self) -> BoxedSystemSet { - self.set.dyn_clone() - } } /// Uninitialized systems associated with a graph node, stored in a [`ScheduleGraph`]. @@ -352,7 +347,7 @@ impl ScheduleGraph { if graph_info.sets.is_empty() { if let Some(default) = self.default_set.as_ref() { - graph_info.sets.insert(default.dyn_clone()); + graph_info.sets.push(default.dyn_clone()); } } @@ -410,7 +405,7 @@ impl ScheduleGraph { 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.insert(default.dyn_clone()); + graph_info.sets.push(default.dyn_clone()); } } @@ -431,7 +426,7 @@ impl ScheduleGraph { } fn check_sets(&mut self, id: &NodeId, graph_info: &GraphInfo) -> Result<(), BuildError> { - for set in graph_info.sets.iter() { + for set in &graph_info.sets { match self.system_set_ids.get(set) { Some(set_id) => { if id == set_id { @@ -448,7 +443,7 @@ impl ScheduleGraph { } fn check_edges(&mut self, id: &NodeId, graph_info: &GraphInfo) -> Result<(), BuildError> { - for (_, set) in graph_info.dependencies.iter() { + for (_, set) in &graph_info.dependencies { match self.system_set_ids.get(set) { Some(set_id) => { if id == set_id { @@ -462,7 +457,7 @@ impl ScheduleGraph { } if let Ambiguity::IgnoreWithSet(ambiguous_with) = &graph_info.ambiguous_with { - for set in ambiguous_with.iter() { + for set in ambiguous_with { if !self.system_set_ids.contains_key(set) { self.add_set(set.dyn_clone()); } @@ -630,7 +625,7 @@ impl ScheduleGraph { .edges_directed(set, Direction::Outgoing) .count(); if systems.len() > 1 && (ambiguities > 0 || dependencies > 0) { - let type_set = self.system_sets[&set].dyn_clone(); + let type_set = self.system_sets[&set].set.dyn_clone(); return Err(BuildError::SystemTypeSetAmbiguity(type_set)); } } diff --git a/crates/bevy_macro_utils/src/lib.rs b/crates/bevy_macro_utils/src/lib.rs index 0f2ff8a659707..890e3b468a310 100644 --- a/crates/bevy_macro_utils/src/lib.rs +++ b/crates/bevy_macro_utils/src/lib.rs @@ -123,7 +123,7 @@ impl BevyManifest { /// /// - `input`: The [`syn::DeriveInput`] for struct that is deriving the label trait /// - `trait_path`: The path [`syn::Path`] to the label trait -pub fn derive_old_style_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStream { +pub fn derive_boxed_label(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStream { let ident = input.ident; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause { From 789cf77b06db9466f819aab3ea15218f1df4181f Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sat, 14 Jan 2023 10:23:05 -0800 Subject: [PATCH 38/64] fix tests --- crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index 07b550f272c20..f12d82770b969 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -153,6 +153,7 @@ impl SystemExecutor for MultiThreadedExecutor { debug_assert!(self.unapplied_systems.is_clear()); self.active_access.clear(); self.evaluated_sets.clear(); + self.skipped_systems.clear(); self.completed_systems.clear(); }; From 910b817d6b513b5d0f6ccb5769486b9326d5a1dd Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sat, 14 Jan 2023 11:30:57 -0800 Subject: [PATCH 39/64] replace `ScheduleGraph` hash maps with vectors --- .../bevy_ecs/src/schedule_v3/graph_utils.rs | 12 +- crates/bevy_ecs/src/schedule_v3/schedule.rs | 175 +++++++++--------- 2 files changed, 94 insertions(+), 93 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/graph_utils.rs b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs index c9ea8da0b949a..55a969dbf94bc 100644 --- a/crates/bevy_ecs/src/schedule_v3/graph_utils.rs +++ b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs @@ -11,11 +11,19 @@ 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(u64), - Set(u64), + System(usize), + Set(usize), } impl NodeId { + /// Returns the internal integer value. + pub fn index(&self) -> usize { + match self { + NodeId::System(index) => *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(_)) diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index ba5c22824846c..50ca14ed5e4b7 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -228,65 +228,58 @@ impl Dag { } } -/// Metadata for a [`SystemSet`], stored in a [`ScheduleGraph`]. -struct SystemSetMeta { - set: BoxedSystemSet, +/// 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 SystemSetMeta { +impl SystemSetNode { pub fn new(set: BoxedSystemSet) -> Self { Self { - set, + inner: set, configured: false, } } pub fn name(&self) -> String { - format!("{:?}", &self.set) + format!("{:?}", &self.inner) } pub fn is_system_type(&self) -> bool { - self.set.is_system_type() + self.inner.is_system_type() } } -/// Uninitialized systems associated with a graph node, stored in a [`ScheduleGraph`]. -enum UninitNode { - System(BoxedSystem, Vec), - SystemSet(Vec), -} - /// Metadata for a [`Schedule`]. #[derive(Default)] struct ScheduleGraph { + systems: Vec>, + system_conditions: Vec>>, + system_sets: Vec, + system_set_conditions: Vec>>, system_set_ids: HashMap, - system_sets: HashMap, - systems: HashMap, - conditions: 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, - next_node_id: u64, changed: bool, - uninit: Vec<(NodeId, UninitNode)>, } 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(), - system_sets: HashMap::new(), - systems: HashMap::new(), - conditions: HashMap::new(), + uninit: Vec::new(), hierarchy: Dag::new(), dependency: Dag::new(), dependency_flattened: Dag::new(), @@ -294,18 +287,10 @@ impl ScheduleGraph { ambiguous_with_flattened: UnGraphMap::new(), ambiguous_with_all: HashSet::new(), default_set: None, - next_node_id: 0, changed: false, - uninit: Vec::new(), } } - fn next_id(&mut self) -> u64 { - let id = self.next_node_id; - self.next_node_id = self.next_node_id.checked_add(1).unwrap(); - id - } - fn set_default_set(&mut self, set: impl SystemSet) { assert!(!set.is_system_type(), "invalid use of system type set"); self.default_set = Some(Box::new(set)); @@ -343,7 +328,7 @@ impl ScheduleGraph { conditions, } = system.into_config(); - let id = NodeId::System(self.next_id()); + let id = NodeId::System(self.systems.len()); if graph_info.sets.is_empty() { if let Some(default) = self.default_set.as_ref() { @@ -355,8 +340,9 @@ impl ScheduleGraph { self.update_graphs(id, graph_info)?; // system init has to be deferred (need `&mut World`) - self.uninit - .push((id, UninitNode::System(system, conditions))); + self.uninit.push((id, 0)); + self.systems.push(Some(system)); + self.system_conditions.push(Some(conditions)); Ok(id) } @@ -387,7 +373,7 @@ impl ScheduleGraph { let SystemSetConfig { set, mut graph_info, - conditions, + mut conditions, } = set.into_config(); let id = match self.system_set_ids.get(&set) { @@ -395,12 +381,10 @@ impl ScheduleGraph { None => self.add_set(set.dyn_clone()), }; - let already_configured = self - .system_sets - .get_mut(&id) - .map_or(false, |meta| std::mem::replace(&mut meta.configured, true)); + let meta = &mut self.system_sets[id.index()]; + let already_configured = std::mem::replace(&mut meta.configured, true); - // system set can be configured multiple times, so this "default check" + // 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() { @@ -413,15 +397,19 @@ impl ScheduleGraph { self.update_graphs(id, graph_info)?; // system init has to be deferred (need `&mut World`) - self.uninit.push((id, UninitNode::SystemSet(conditions))); + 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.next_id()); - self.system_set_ids.insert(set.dyn_clone(), id); - self.system_sets.insert(id, SystemSetMeta::new(set)); + 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 } @@ -520,26 +508,22 @@ impl ScheduleGraph { } fn initialize(&mut self, world: &mut World) { - for (id, uninit) in self.uninit.drain(..) { - match uninit { - UninitNode::System(mut system, mut conditions) => { - debug_assert!(id.is_system()); - system.initialize(world); - self.systems.insert(id, system); - for condition in &mut conditions { - condition.initialize(world); - } - self.conditions.insert(id, conditions); + for (id, i) in self.uninit.drain(..) { + match id { + NodeId::System(index) => { + self.systems[index].as_mut().unwrap().initialize(world); + self.system_conditions[index].as_mut().map(|v| { + for condition in v.iter_mut() { + condition.initialize(world); + } + }); } - UninitNode::SystemSet(mut conditions) => { - debug_assert!(id.is_set()); - for condition in &mut conditions { - condition.initialize(world); - } - self.conditions - .entry(id) - .or_insert_with(Vec::new) - .append(&mut conditions); + NodeId::Set(index) => { + self.system_set_conditions[index].as_mut().map(|v| { + for condition in v.iter_mut().skip(i) { + condition.initialize(world); + } + }); } } } @@ -582,7 +566,7 @@ impl ScheduleGraph { } // map system sets to all their member systems - let mut systems = HashMap::with_capacity(self.system_sets.len()); + 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() { @@ -590,7 +574,7 @@ impl ScheduleGraph { } let set = id; - systems.insert(set, Vec::new()); + systems_in_sets.insert(set, Vec::new()); for child in self .hierarchy @@ -599,10 +583,11 @@ impl ScheduleGraph { { match child { NodeId::System(_) => { - systems.get_mut(&set).unwrap().push(child); + systems_in_sets.get_mut(&set).unwrap().push(child); } NodeId::Set(_) => { - let [sys, child_sys] = systems.get_many_mut([&set, &child]).unwrap(); + let [sys, child_sys] = + systems_in_sets.get_many_mut([&set, &child]).unwrap(); sys.extend_from_slice(child_sys); } } @@ -610,8 +595,9 @@ impl ScheduleGraph { } // can't depend on or be ambiguous with system type sets that have many instances - for (&set, systems) in systems.iter() { - if self.system_sets[&set].is_system_type() { + 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 @@ -625,8 +611,7 @@ impl ScheduleGraph { .edges_directed(set, Direction::Outgoing) .count(); if systems.len() > 1 && (ambiguities > 0 || dependencies > 0) { - let type_set = self.system_sets[&set].set.dyn_clone(); - return Err(BuildError::SystemTypeSetAmbiguity(type_set)); + return Err(BuildError::SystemTypeSetAmbiguity(node.inner.dyn_clone())); } } } @@ -644,18 +629,18 @@ impl ScheduleGraph { dependency_flattened.add_edge(lhs, rhs, ()); } (NodeId::Set(_), NodeId::System(_)) => { - for &lhs_ in &systems[&lhs] { + for &lhs_ in &systems_in_sets[&lhs] { dependency_flattened.add_edge(lhs_, rhs, ()); } } (NodeId::System(_), NodeId::Set(_)) => { - for &rhs_ in &systems[&rhs] { + for &rhs_ in &systems_in_sets[&rhs] { dependency_flattened.add_edge(lhs, rhs_, ()); } } (NodeId::Set(_), NodeId::Set(_)) => { - for &lhs_ in &systems[&lhs] { - for &rhs_ in &systems[&rhs] { + for &lhs_ in &systems_in_sets[&lhs] { + for &rhs_ in &systems_in_sets[&rhs] { dependency_flattened.add_edge(lhs_, rhs_, ()); } } @@ -687,18 +672,18 @@ impl ScheduleGraph { ambiguous_with_flattened.add_edge(lhs, rhs, ()); } (NodeId::Set(_), NodeId::System(_)) => { - for &lhs_ in &systems[&lhs] { + for &lhs_ in &systems_in_sets[&lhs] { ambiguous_with_flattened.add_edge(lhs_, rhs, ()); } } (NodeId::System(_), NodeId::Set(_)) => { - for &rhs_ in &systems[&rhs] { + for &rhs_ in &systems_in_sets[&rhs] { ambiguous_with_flattened.add_edge(lhs, rhs_, ()); } } (NodeId::Set(_), NodeId::Set(_)) => { - for &lhs_ in &systems[&lhs] { - for &rhs_ in &systems[&rhs] { + for &lhs_ in &systems_in_sets[&lhs] { + for &rhs_ in &systems_in_sets[&rhs] { ambiguous_with_flattened.add_edge(lhs_, rhs_, ()); } } @@ -718,8 +703,8 @@ impl ScheduleGraph { continue; } - let system_a = self.systems.get(&a).unwrap(); - let system_b = self.systems.get(&b).unwrap(); + 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 { @@ -764,7 +749,11 @@ impl ScheduleGraph { .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.conditions.get(&id).filter(|v| !v.is_empty()).is_some() + id.is_set() + && self.system_set_conditions[id.index()] + .as_ref() + .filter(|v| !v.is_empty()) + .is_some() }) .unzip(); @@ -845,8 +834,8 @@ impl ScheduleGraph { .zip(schedule.systems.drain(..)) .zip(schedule.system_conditions.drain(..)) { - self.systems.insert(id, system.into_inner()); - self.conditions.insert(id, conditions.into_inner()); + self.systems[id.index()] = Some(system.into_inner()); + self.system_conditions[id.index()] = Some(conditions.into_inner()); } for (id, conditions) in schedule @@ -854,21 +843,21 @@ impl ScheduleGraph { .drain(..) .zip(schedule.set_conditions.drain(..)) { - self.conditions.insert(id, conditions.into_inner()); + self.system_set_conditions[id.index()] = Some(conditions.into_inner()); } *schedule = self.build_schedule()?; // move systems into new schedule for &id in &schedule.system_ids { - let system = self.systems.remove(&id).unwrap(); - let conditions = self.conditions.remove(&id).unwrap(); + let system = self.systems[id.index()].take().unwrap(); + let conditions = self.system_conditions[id.index()].take().unwrap(); schedule.systems.push(RefCell::new(system)); schedule.system_conditions.push(RefCell::new(conditions)); } for &id in &schedule.set_ids { - let conditions = self.conditions.remove(&id).unwrap(); + let conditions = self.system_set_conditions[id.index()].take().unwrap(); schedule.set_conditions.push(RefCell::new(conditions)); } @@ -880,8 +869,12 @@ impl ScheduleGraph { impl ScheduleGraph { fn get_node_name(&self, id: &NodeId) -> String { match id { - NodeId::System(_) => self.systems[id].name().to_string(), - NodeId::Set(_) => self.system_sets[id].name(), + NodeId::System(_) => self.systems[id.index()] + .as_ref() + .unwrap() + .name() + .to_string(), + NodeId::Set(_) => self.system_sets[id.index()].name(), } } From 470cce1ac96eb270f44a8f2fb52ad693cfc476d4 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sat, 14 Jan 2023 16:41:14 -0800 Subject: [PATCH 40/64] make optional errors opt-in --- crates/bevy_ecs/src/schedule_v3/config.rs | 2 +- crates/bevy_ecs/src/schedule_v3/mod.rs | 38 +++-- crates/bevy_ecs/src/schedule_v3/schedule.rs | 146 ++++++++++++++++---- 3 files changed, 152 insertions(+), 34 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/config.rs b/crates/bevy_ecs/src/schedule_v3/config.rs index 5b80af4a7ee3d..f45e258e0bc2d 100644 --- a/crates/bevy_ecs/src/schedule_v3/config.rs +++ b/crates/bevy_ecs/src/schedule_v3/config.rs @@ -1,4 +1,4 @@ -use bevy_utils::prelude::default; +use bevy_utils::default; use crate::{ schedule_v3::{ diff --git a/crates/bevy_ecs/src/schedule_v3/mod.rs b/crates/bevy_ecs/src/schedule_v3/mod.rs index 52077a50b3e90..6e0be6ca480df 100644 --- a/crates/bevy_ecs/src/schedule_v3/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/mod.rs @@ -318,7 +318,7 @@ mod tests { schedule.configure_set(TestSet::B.after(TestSet::A)); let result = schedule.initialize(&mut world); - assert!(matches!(result, Err(BuildError::DependencyCycle))); + assert!(matches!(result, Err(ScheduleBuildError::DependencyCycle))); fn foo() {} fn bar() {} @@ -328,7 +328,7 @@ mod tests { schedule.add_systems((foo.after(bar), bar.after(foo))); let result = schedule.initialize(&mut world); - assert!(matches!(result, Err(BuildError::DependencyCycle))); + assert!(matches!(result, Err(ScheduleBuildError::DependencyCycle))); } #[test] @@ -347,7 +347,7 @@ mod tests { schedule.configure_set(TestSet::B.in_set(TestSet::A)); let result = schedule.initialize(&mut world); - assert!(matches!(result, Err(BuildError::HierarchyCycle))); + assert!(matches!(result, Err(ScheduleBuildError::HierarchyCycle))); } #[test] @@ -373,7 +373,10 @@ mod tests { // 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(BuildError::SystemTypeSetAmbiguity(_)))); + assert!(matches!( + result, + Err(ScheduleBuildError::SystemTypeSetAmbiguity(_)) + )); // same goes for `ambiguous_with` let mut schedule = Schedule::new(); @@ -383,7 +386,10 @@ mod tests { assert!(result.is_ok()); schedule.add_system(foo); let result = schedule.initialize(&mut world); - assert!(matches!(result, Err(BuildError::SystemTypeSetAmbiguity(_)))); + assert!(matches!( + result, + Err(ScheduleBuildError::SystemTypeSetAmbiguity(_)) + )); } #[test] @@ -405,10 +411,14 @@ mod tests { } #[test] - fn hierarchy_conflict() { + 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); @@ -420,7 +430,10 @@ mod tests { // `X` cannot be the `A`'s child and grandchild at the same time. let result = schedule.initialize(&mut world); - assert!(matches!(result, Err(BuildError::HierarchyConflict))); + assert!(matches!( + result, + Err(ScheduleBuildError::HierarchyRedundancy) + )); } #[test] @@ -432,7 +445,10 @@ mod tests { 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(BuildError::CrossDependency(_, _)))); + assert!(matches!( + result, + Err(ScheduleBuildError::CrossDependency(_, _)) + )); } #[test] @@ -446,9 +462,13 @@ mod tests { 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(BuildError::Ambiguity))); + 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 index 50ca14ed5e4b7..bd70252f5b21e 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -4,6 +4,7 @@ use std::{ result::Result, }; +use bevy_utils::default; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; use bevy_utils::{ @@ -135,13 +136,19 @@ impl Schedule { self } - /// Sets the system set that new systems and system sets will join by default + /// 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.set_build_settings(settings); + self + } + /// Returns the schedule's current execution strategy. pub fn get_executor_kind(&self) -> ExecutorKind { self.executor.kind() @@ -172,7 +179,7 @@ impl Schedule { /// 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<(), BuildError> { + 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)?; @@ -269,6 +276,7 @@ struct ScheduleGraph { ambiguous_with_all: HashSet, default_set: Option, changed: bool, + settings: ScheduleBuildSettings, } impl ScheduleGraph { @@ -288,6 +296,7 @@ impl ScheduleGraph { ambiguous_with_all: HashSet::new(), default_set: None, changed: false, + settings: default(), } } @@ -296,6 +305,10 @@ impl ScheduleGraph { self.default_set = Some(Box::new(set)); } + fn set_build_settings(&mut self, settings: ScheduleBuildSettings) { + self.settings = settings; + } + fn add_systems

(&mut self, systems: impl IntoSystemConfigs

) { let SystemConfigs { systems, chained } = systems.into_configs(); let mut system_iter = systems.into_iter(); @@ -321,7 +334,7 @@ impl ScheduleGraph { fn add_system_inner

( &mut self, system: impl IntoSystemConfig

, - ) -> Result { + ) -> Result { let SystemConfig { system, mut graph_info, @@ -369,7 +382,10 @@ impl ScheduleGraph { self.configure_set_inner(set).unwrap(); } - fn configure_set_inner(&mut self, set: impl IntoSystemSetConfig) -> Result { + fn configure_set_inner( + &mut self, + set: impl IntoSystemSetConfig, + ) -> Result { let SystemSetConfig { set, mut graph_info, @@ -413,12 +429,16 @@ impl ScheduleGraph { id } - fn check_sets(&mut self, id: &NodeId, graph_info: &GraphInfo) -> Result<(), BuildError> { + 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(BuildError::HierarchyLoop(set.dyn_clone())); + return Err(ScheduleBuildError::HierarchyLoop(set.dyn_clone())); } } None => { @@ -430,12 +450,16 @@ impl ScheduleGraph { Ok(()) } - fn check_edges(&mut self, id: &NodeId, graph_info: &GraphInfo) -> Result<(), BuildError> { + fn check_edges( + &mut self, + id: &NodeId, + graph_info: &GraphInfo, + ) -> Result<(), ScheduleBuildError> { for (_, set) in &graph_info.dependencies { match self.system_set_ids.get(set) { Some(set_id) => { if id == set_id { - return Err(BuildError::DependencyLoop(set.dyn_clone())); + return Err(ScheduleBuildError::DependencyLoop(set.dyn_clone())); } } None => { @@ -455,7 +479,11 @@ impl ScheduleGraph { Ok(()) } - fn update_graphs(&mut self, id: NodeId, graph_info: GraphInfo) -> Result<(), BuildError> { + 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; @@ -529,12 +557,12 @@ impl ScheduleGraph { } } - fn build_schedule(&mut self) -> Result { + 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(BuildError::HierarchyCycle); + return Err(ScheduleBuildError::HierarchyCycle); } self.hierarchy.topsort = hier_scc.into_iter().flatten().rev().collect::>(); @@ -542,14 +570,19 @@ impl ScheduleGraph { 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); - return Err(BuildError::HierarchyConflict); + 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(BuildError::DependencyCycle); + return Err(ScheduleBuildError::DependencyCycle); } self.dependency.topsort = dep_scc.into_iter().flatten().rev().collect::>(); @@ -561,7 +594,7 @@ impl ScheduleGraph { { let name_a = self.get_node_name(&a); let name_b = self.get_node_name(&b); - return Err(BuildError::CrossDependency(name_a, name_b)); + return Err(ScheduleBuildError::CrossDependency(name_a, name_b)); } } @@ -611,7 +644,9 @@ impl ScheduleGraph { .edges_directed(set, Direction::Outgoing) .count(); if systems.len() > 1 && (ambiguities > 0 || dependencies > 0) { - return Err(BuildError::SystemTypeSetAmbiguity(node.inner.dyn_clone())); + return Err(ScheduleBuildError::SystemTypeSetAmbiguity( + node.inner.dyn_clone(), + )); } } } @@ -623,6 +658,7 @@ impl ScheduleGraph { dependency_flattened.add_node(id); } } + for (lhs, rhs, _) in self.dependency.graph.all_edges() { match (lhs, rhs) { (NodeId::System(_), NodeId::System(_)) => { @@ -652,7 +688,7 @@ impl ScheduleGraph { let flat_scc = tarjan_scc(&dependency_flattened); if self.contains_cycles(&flat_scc) { self.report_cycles(&flat_scc); - return Err(BuildError::DependencyCycle); + return Err(ScheduleBuildError::DependencyCycle); } self.dependency_flattened.graph = dependency_flattened; @@ -664,6 +700,9 @@ impl ScheduleGraph { &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() { @@ -719,7 +758,9 @@ impl ScheduleGraph { if self.contains_conflicts(&conflicting_systems) { self.report_conflicts(&conflicting_systems); - return Err(BuildError::Ambiguity); + if matches!(self.settings.ambiguity_detection, LogLevel::Error) { + return Err(ScheduleBuildError::Ambiguity); + } } // build the schedule @@ -822,9 +863,9 @@ impl ScheduleGraph { }) } - fn update_schedule(&mut self, schedule: &mut SystemSchedule) -> Result<(), BuildError> { + fn update_schedule(&mut self, schedule: &mut SystemSchedule) -> Result<(), ScheduleBuildError> { if !self.uninit.is_empty() { - return Err(BuildError::Uninitialized); + return Err(ScheduleBuildError::Uninitialized); } // move systems out of old schedule @@ -979,25 +1020,82 @@ impl ScheduleGraph { } } +/// Category of errors encountered during schedule construction. #[derive(Error, Debug)] #[non_exhaustive] -pub enum BuildError { +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, - #[error("System set hierarchy contains conflicting relationships.")] - HierarchyConflict, + /// 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, - #[error("`{0:?}` and `{1:?}` have both `in_set` and `before`-`after` relationships (possibly transitive). This combination is unsolvable as a system cannot run before or after a set it belongs to.")] + /// 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, - #[error("Schedule is not initialized.")] + /// Tried to run a schedule before all of its systems have been initialized. + #[error("Systems in schedule have not been initialized.")] Uninitialized, } + +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 + } +} From 3d0224d90ffc4dc1d0d11b03a1b44ee675f829e1 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sat, 14 Jan 2023 16:51:07 -0800 Subject: [PATCH 41/64] clippy --- crates/bevy_ecs/src/schedule_v3/graph_utils.rs | 3 +-- crates/bevy_ecs/src/schedule_v3/schedule.rs | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/graph_utils.rs b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs index 55a969dbf94bc..68bffb81313d7 100644 --- a/crates/bevy_ecs/src/schedule_v3/graph_utils.rs +++ b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs @@ -19,8 +19,7 @@ impl NodeId { /// Returns the internal integer value. pub fn index(&self) -> usize { match self { - NodeId::System(index) => *index, - NodeId::Set(index) => *index, + NodeId::System(index) | NodeId::Set(index) => *index, } } diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index bd70252f5b21e..a3a5d9736615e 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -540,18 +540,18 @@ impl ScheduleGraph { match id { NodeId::System(index) => { self.systems[index].as_mut().unwrap().initialize(world); - self.system_conditions[index].as_mut().map(|v| { + if let Some(v) = self.system_conditions[index].as_mut() { for condition in v.iter_mut() { condition.initialize(world); } - }); + } } NodeId::Set(index) => { - self.system_set_conditions[index].as_mut().map(|v| { + if let Some(v) = self.system_set_conditions[index].as_mut() { for condition in v.iter_mut().skip(i) { condition.initialize(world); } - }); + } } } } From b465063cea752e4834885c4bf9681d015cf94b9b Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sat, 14 Jan 2023 17:53:41 -0800 Subject: [PATCH 42/64] Apply suggestions from code review Co-authored-by: Alice Cecile --- crates/bevy_ecs/src/schedule_v3/migration.rs | 4 ++-- crates/bevy_ecs/src/schedule_v3/schedule.rs | 2 +- crates/bevy_ecs/src/world/mod.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/migration.rs b/crates/bevy_ecs/src/schedule_v3/migration.rs index 8e95b28174fb0..967d6bdb7a85f 100644 --- a/crates/bevy_ecs/src/schedule_v3/migration.rs +++ b/crates/bevy_ecs/src/schedule_v3/migration.rs @@ -1,7 +1,7 @@ use crate::schedule_v3::*; use crate::world::World; -/// New "stageless" `App` methods. +/// 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. @@ -22,7 +22,7 @@ pub trait AppExt { fn add_state(&mut self) -> &mut Self; } -/// New "stageless" [`World`] methods. +/// Temporary "stageless" [`World`] methods. pub trait WorldExt { /// Runs the [`Schedule`] associated with `label`. fn run_schedule(&mut self, label: impl ScheduleLabel); diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index a3a5d9736615e..4e265e29df3d6 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -50,7 +50,7 @@ impl Schedules { self.inner.insert(label, schedule) } - /// Removes the `label` entry from the map, returning its schedule if one existed. + /// 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); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index d9cfcfe68195f..3ddeba0a052e0 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1590,7 +1590,7 @@ impl World { /// /// **Note:** Does nothing if the [`World`] counter has not been incremented at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD) /// times since the previous pass. - // TODO: perf + // TODO: benchmark and optimize pub fn check_change_ticks(&mut self) { let last_check_tick = self.last_check_tick; let change_tick = self.change_tick(); From d987a082f06f02806170ad8da2327992d08fd38b Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sat, 14 Jan 2023 17:55:35 -0800 Subject: [PATCH 43/64] Apply suggestions from code review --- crates/bevy_ecs/src/schedule_v3/config.rs | 25 +++++++++++++++---- .../bevy_ecs/src/schedule_v3/executor/mod.rs | 7 +++++- .../schedule_v3/executor/multi_threaded.rs | 25 ++++++++++--------- .../schedule_v3/executor/single_threaded.rs | 3 +++ .../bevy_ecs/src/schedule_v3/graph_utils.rs | 5 ++-- crates/bevy_ecs/src/schedule_v3/mod.rs | 4 --- crates/bevy_ecs/src/schedule_v3/schedule.rs | 6 ++++- crates/bevy_ecs/src/schedule_v3/set.rs | 4 +-- crates/bevy_utils/src/label.rs | 6 ++--- 9 files changed, 55 insertions(+), 30 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/config.rs b/crates/bevy_ecs/src/schedule_v3/config.rs index f45e258e0bc2d..509a66db52e0c 100644 --- a/crates/bevy_ecs/src/schedule_v3/config.rs +++ b/crates/bevy_ecs/src/schedule_v3/config.rs @@ -38,7 +38,10 @@ pub(super) fn new_set_unchecked(set: BoxedSystemSet) -> SystemSetConfig { fn new_set(set: BoxedSystemSet) -> SystemSetConfig { // system type sets are automatically populated // to avoid unintentionally broad changes, they cannot be configured - assert!(!set.is_system_type(), "invalid use of system type set"); + assert!( + !set.is_system_type(), + "configuring system type sets is not allowed" + ); new_set_unchecked(set) } @@ -162,7 +165,10 @@ impl IntoSystemSetConfig for SystemSetConfig { } fn in_set(mut self, set: impl SystemSet) -> Self { - assert!(!set.is_system_type(), "invalid use of system type set"); + 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 } @@ -306,7 +312,10 @@ impl IntoSystemConfig<()> for SystemConfig { } fn in_set(mut self, set: impl SystemSet) -> Self { - assert!(!set.is_system_type(), "invalid use of system type set"); + 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 } @@ -425,7 +434,10 @@ impl IntoSystemConfigs<()> for SystemConfigs { } fn in_set(mut self, set: impl SystemSet) -> Self { - assert!(!set.is_system_type(), "invalid use of system type set"); + 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()); } @@ -508,7 +520,10 @@ impl IntoSystemSetConfigs for SystemSetConfigs { } fn in_set(mut self, set: impl SystemSet) -> Self { - assert!(!set.is_system_type(), "invalid use of system type set"); + 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()); } diff --git a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs index dfbc4fde85363..98e1125fceae0 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs @@ -29,6 +29,9 @@ pub(super) trait SystemExecutor: Send + Sync { #[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. @@ -38,6 +41,8 @@ pub enum ExecutorKind { MultiThreaded, } +// NOTE: This type and the executor implementations were designed as a pair. +// Do not make them pub. #[derive(Default)] pub(super) struct SystemSchedule { pub(super) systems: Vec>, @@ -70,7 +75,7 @@ impl SystemSchedule { // SAFETY: // - MultiThreadedExecutor is the only type that uses SystemSchedule across multiple threads. // - MultiThreadedExecutor cannot alias any of the RefCells. -// - SystemSchedule is not made pub in any way +// - SystemSchedule is not made pub in any way. unsafe impl Sync for SystemSchedule {} /// Instructs the executor to call [`apply_buffers`](crate::system::System::apply_buffers) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index f12d82770b969..07eb9a0a7e9f2 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -18,7 +18,7 @@ use crate::{ /// Per-system data used by the [`MultiThreadedExecutor`]. // Copied here because it can't be read from the system when it's running. -struct SystemTaskMeta { +struct SystemTaskMetadata { /// Indices of the systems that directly depend on the system. dependents: Vec, /// The `ArchetypeComponentId` access of the system. @@ -36,7 +36,7 @@ pub struct MultiThreadedExecutor { /// Receives system completion events. receiver: Receiver, /// Metadata for scheduling and running system tasks. - system_task_meta: Vec, + system_task_metadata: Vec, /// The number of dependencies each system has that have not completed. dependencies_remaining: Vec, /// Union of the accesses of all currently running systems. @@ -82,10 +82,10 @@ impl SystemExecutor for MultiThreadedExecutor { self.skipped_systems = FixedBitSet::with_capacity(sys_count); self.unapplied_systems = FixedBitSet::with_capacity(sys_count); - self.system_task_meta = Vec::with_capacity(sys_count); + self.system_task_metadata = Vec::with_capacity(sys_count); for index in 0..sys_count { let system = schedule.systems[index].borrow(); - self.system_task_meta.push(SystemTaskMeta { + self.system_task_metadata.push(SystemTaskMetadata { dependents: schedule.system_dependents[index].clone(), archetype_component_access: default(), is_send: system.is_send(), @@ -172,7 +172,7 @@ impl MultiThreadedExecutor { Self { sender, receiver, - system_task_meta: Vec::new(), + system_task_metadata: Vec::new(), dependencies_remaining: Vec::new(), active_access: default(), local_thread_running: false, @@ -215,7 +215,7 @@ impl MultiThreadedExecutor { // system is starting self.running_systems.insert(system_index); - if self.system_task_meta[system_index].is_exclusive { + if self.system_task_metadata[system_index].is_exclusive { // SAFETY: `can_run` confirmed no other systems are running let world = unsafe { &mut *cell.get() }; self.spawn_exclusive_system_task(scope, system_index, schedule, world); @@ -233,7 +233,7 @@ impl MultiThreadedExecutor { let _span = info_span!("check_access", name = &*name).entered(); // TODO: an earlier out if archetypes did not change - let system_meta = &mut self.system_task_meta[system_index]; + let system_meta = &mut self.system_task_metadata[system_index]; if system_meta.is_exclusive && !self.running_systems.is_clear() { return false; @@ -366,7 +366,7 @@ impl MultiThreadedExecutor { #[cfg(feature = "trace")] let task = task.instrument(task_span); - let system_meta = &self.system_task_meta[system_index]; + let system_meta = &self.system_task_metadata[system_index]; self.active_access .extend(&system_meta.archetype_component_access); @@ -435,11 +435,11 @@ impl MultiThreadedExecutor { } fn finish_system_and_signal_dependents(&mut self, system_index: usize) { - if !self.system_task_meta[system_index].is_send { + if !self.system_task_metadata[system_index].is_send { self.local_thread_running = false; } - if self.system_task_meta[system_index].is_exclusive { + if self.system_task_metadata[system_index].is_exclusive { self.exclusive_running = false; } @@ -457,8 +457,9 @@ impl MultiThreadedExecutor { 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_meta[system_index].dependents { + for &dep_idx in &self.system_task_metadata[system_index].dependents { let dependencies = &mut self.dependencies_remaining[dep_idx]; + debug_assert!(*dependencies >= 1); *dependencies -= 1; if *dependencies == 0 && !self.completed_systems.contains(dep_idx) { self.ready_systems.insert(dep_idx); @@ -469,7 +470,7 @@ impl MultiThreadedExecutor { fn rebuild_active_access(&mut self) { self.active_access.clear(); for index in self.running_systems.ones() { - let system_meta = &self.system_task_meta[index]; + let system_meta = &self.system_task_metadata[index]; self.active_access .extend(&system_meta.archetype_component_access); } diff --git a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs index b0804b13ddd85..9d8cd3ebb083b 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs @@ -10,6 +10,9 @@ use crate::{ }; /// 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. diff --git a/crates/bevy_ecs/src/schedule_v3/graph_utils.rs b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs index 68bffb81313d7..aedfc46ef1814 100644 --- a/crates/bevy_ecs/src/schedule_v3/graph_utils.rs +++ b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs @@ -72,6 +72,7 @@ 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, @@ -82,9 +83,9 @@ pub(crate) struct CheckGraphResults { /// Edges that are redundant because a longer path exists. pub(crate) transitive_edges: Vec<(V, V)>, /// Variant of the graph with no transitive edges. - #[allow(dead_code)] 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, } @@ -102,7 +103,7 @@ impl Default for CheckGraphResults { } } -/// Processes a DAG and computes: +/// Processes a DAG and computes its: /// - transitive reduction (along with the set of removed edges) /// - transitive closure /// - reachability matrix (as a bitset) diff --git a/crates/bevy_ecs/src/schedule_v3/mod.rs b/crates/bevy_ecs/src/schedule_v3/mod.rs index 6e0be6ca480df..65b4de80a3140 100644 --- a/crates/bevy_ecs/src/schedule_v3/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/mod.rs @@ -471,8 +471,4 @@ mod tests { assert!(matches!(result, Err(ScheduleBuildError::Ambiguity))); } } - - mod system_ambiguity_errors { - // TODO - } } diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index 4e265e29df3d6..16805593fa356 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -301,7 +301,10 @@ impl ScheduleGraph { } fn set_default_set(&mut self, set: impl SystemSet) { - assert!(!set.is_system_type(), "invalid use of system type set"); + assert!( + !set.is_system_type(), + "adding arbitrary systems to a system type set is not allowed" + ); self.default_set = Some(Box::new(set)); } @@ -1057,6 +1060,7 @@ pub enum ScheduleBuildError { Uninitialized, } +/// Specifies how schedule construction should respond to detecting a certain kind of issue. pub enum LogLevel { /// Occurrences are logged only. Warn, diff --git a/crates/bevy_ecs/src/schedule_v3/set.rs b/crates/bevy_ecs/src/schedule_v3/set.rs index d6a7247a77ebb..d62a9ba5267f0 100644 --- a/crates/bevy_ecs/src/schedule_v3/set.rs +++ b/crates/bevy_ecs/src/schedule_v3/set.rs @@ -3,7 +3,7 @@ use std::hash::{Hash, Hasher}; use std::marker::PhantomData; pub use bevy_ecs_macros::{ScheduleLabel, SystemSet}; -use bevy_utils::define_old_style_label; +use bevy_utils::define_boxed_label; use bevy_utils::label::DynHash; use crate::system::{ @@ -11,7 +11,7 @@ use crate::system::{ IsFunctionSystem, SystemParam, SystemParamFunction, }; -define_old_style_label!(ScheduleLabel); +define_boxed_label!(ScheduleLabel); pub type BoxedSystemSet = Box; pub type BoxedScheduleLabel = Box; diff --git a/crates/bevy_utils/src/label.rs b/crates/bevy_utils/src/label.rs index 1155574f0f7bc..d3e816552bb1e 100644 --- a/crates/bevy_utils/src/label.rs +++ b/crates/bevy_utils/src/label.rs @@ -65,11 +65,11 @@ where /// # Example /// /// ``` -/// # use bevy_utils::define_old_style_label; -/// define_old_style_label!(MyNewLabelTrait); +/// # use bevy_utils::define_boxed_label; +/// define_boxed_label!(MyNewLabelTrait); /// ``` #[macro_export] -macro_rules! define_old_style_label { +macro_rules! define_boxed_label { ($label_trait_name:ident) => { /// A strongly-typed label. pub trait $label_trait_name: From 533e266882f45d67755d22f884a849d310b78c97 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sat, 14 Jan 2023 18:06:09 -0800 Subject: [PATCH 44/64] wording --- crates/bevy_ecs/src/schedule_v3/executor/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs index 98e1125fceae0..f4425b39cffc0 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs @@ -42,7 +42,7 @@ pub enum ExecutorKind { } // NOTE: This type and the executor implementations were designed as a pair. -// Do not make them pub. +// To maintain certain safety invariants, it's important to keep this type private. #[derive(Default)] pub(super) struct SystemSchedule { pub(super) systems: Vec>, From c8f0a98f5e138bbeffbf3fba569ab65986bb68ce Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sat, 14 Jan 2023 21:37:21 -0800 Subject: [PATCH 45/64] *mildly* cursed code --- .../bevy_ecs/src/schedule_v3/executor/mod.rs | 18 +- .../schedule_v3/executor/multi_threaded.rs | 192 +++++++++++------- .../src/schedule_v3/executor/simple.rs | 16 +- .../schedule_v3/executor/single_threaded.rs | 18 +- crates/bevy_ecs/src/schedule_v3/schedule.rs | 19 +- crates/bevy_utils/src/syncunsafecell.rs | 8 + 6 files changed, 150 insertions(+), 121 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs index f4425b39cffc0..f69675e91ad59 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs @@ -6,8 +6,6 @@ pub use self::multi_threaded::MultiThreadedExecutor; pub use self::simple::SimpleExecutor; pub use self::single_threaded::SingleThreadedExecutor; -use std::cell::RefCell; - use fixedbitset::FixedBitSet; use crate::{ @@ -41,13 +39,13 @@ pub enum ExecutorKind { MultiThreaded, } -// NOTE: This type and the executor implementations were designed as a pair. -// To maintain certain safety invariants, it's important to keep this type private. +/// Holds systems and conditions of a [`Schedule`](super::Schedule) sorted in topological order +/// (along with dependency information for multi-threaded execution). #[derive(Default)] pub(super) struct SystemSchedule { - pub(super) systems: Vec>, - pub(super) system_conditions: Vec>>, - pub(super) set_conditions: Vec>>, + 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, @@ -72,12 +70,6 @@ impl SystemSchedule { } } -// SAFETY: -// - MultiThreadedExecutor is the only type that uses SystemSchedule across multiple threads. -// - MultiThreadedExecutor cannot alias any of the RefCells. -// - SystemSchedule is not made pub in any way. -unsafe impl Sync for SystemSchedule {} - /// Instructs the executor to call [`apply_buffers`](crate::system::System::apply_buffers) /// on the systems that have run but not applied their buffers. /// diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index 07eb9a0a7e9f2..57af979b2000c 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -13,16 +13,44 @@ use crate::{ 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 { - /// Indices of the systems that directly depend on the system. - dependents: Vec, /// 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. @@ -84,12 +112,11 @@ impl SystemExecutor for MultiThreadedExecutor { self.system_task_metadata = Vec::with_capacity(sys_count); for index in 0..sys_count { - let system = schedule.systems[index].borrow(); self.system_task_metadata.push(SystemTaskMetadata { - dependents: schedule.system_dependents[index].clone(), archetype_component_access: default(), - is_send: system.is_send(), - is_exclusive: system.is_exclusive(), + dependents: schedule.system_dependents[index].clone(), + is_send: schedule.systems[index].is_send(), + is_exclusive: schedule.systems[index].is_exclusive(), }); } @@ -97,33 +124,41 @@ impl SystemExecutor for MultiThreadedExecutor { } fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) { + // reset counts + self.dependencies_remaining.clear(); + self.dependencies_remaining + .extend_from_slice(&schedule.system_dependencies); + + for (system_index, dependencies) in self.dependencies_remaining.iter_mut().enumerate() { + if *dependencies == 0 { + self.ready_systems.insert(system_index); + } + } + + // using spare bitset to avoid repeated allocations + let mut ready_systems = FixedBitSet::with_capacity(self.ready_systems.len()); + + 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 { - // reset counts - self.dependencies_remaining.clear(); - self.dependencies_remaining - .extend_from_slice(&schedule.system_dependencies); - - for (system_index, dependencies) in - self.dependencies_remaining.iter_mut().enumerate() - { - if *dependencies == 0 { - self.ready_systems.insert(system_index); - } - } - - // using spare bitset to avoid repeated allocations - let mut ready_systems = FixedBitSet::with_capacity(self.ready_systems.len()); - - // main loop - let world = SyncUnsafeCell::from_mut(world); while self.completed_systems.count_ones(..) != self.completed_systems.len() { if !self.exclusive_running { ready_systems.clear(); ready_systems.union_with(&self.ready_systems); - self.spawn_system_tasks(&ready_systems, scope, schedule, world); + self.spawn_system_tasks( + scope, + systems, + &mut conditions, + world, + &ready_systems, + ); } if !self.running_systems.is_clear() { @@ -146,7 +181,7 @@ impl SystemExecutor for MultiThreadedExecutor { // SAFETY: all systems have completed let world = unsafe { &mut *world.get() }; - Self::apply_system_buffers(&mut self.unapplied_systems, schedule, world); + apply_system_buffers(&mut self.unapplied_systems, systems, world); debug_assert!(self.ready_systems.is_clear()); debug_assert!(self.running_systems.is_clear()); @@ -188,15 +223,16 @@ impl MultiThreadedExecutor { fn spawn_system_tasks<'scope>( &mut self, - ready_systems: &FixedBitSet, scope: &Scope<'_, 'scope, ()>, - schedule: &'scope SystemSchedule, + systems: &'scope [SyncUnsafeCell], + conditions: &mut Conditions, cell: &'scope SyncUnsafeCell, + ready_systems: &FixedBitSet, ) { for system_index in ready_systems.ones() { // SAFETY: no exclusive system is running let world = unsafe { &*cell.get() }; - if !self.can_run(system_index, schedule, world) { + if !self.can_run(system_index, systems, 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 @@ -207,7 +243,7 @@ impl MultiThreadedExecutor { // system is either going to run or be skipped self.ready_systems.set(system_index, false); - if !self.should_run(system_index, schedule, world) { + if !self.should_run(system_index, systems, conditions, world) { self.skip_system_and_signal_dependents(system_index); continue; } @@ -218,19 +254,26 @@ impl MultiThreadedExecutor { if self.system_task_metadata[system_index].is_exclusive { // SAFETY: `can_run` confirmed no other systems are running let world = unsafe { &mut *cell.get() }; - self.spawn_exclusive_system_task(scope, system_index, schedule, world); + self.spawn_exclusive_system_task(scope, system_index, systems, world); break; } - self.spawn_system_task(scope, system_index, schedule, world); + self.spawn_system_task(scope, system_index, systems, world); } } - fn can_run(&mut self, system_index: usize, schedule: &SystemSchedule, world: &World) -> bool { + fn can_run( + &mut self, + system_index: usize, + systems: &[SyncUnsafeCell], + conditions: &mut Conditions, + world: &World, + ) -> bool { #[cfg(feature = "trace")] - let name = schedule.systems[system_index].borrow().name(); + // SAFETY: this system is not running, no other reference exists + let system = unsafe { &*systems[system_index].get() }; #[cfg(feature = "trace")] - let _span = info_span!("check_access", name = &*name).entered(); + let _span = info_span!("check_access", name = &*system.name()).entered(); // TODO: an earlier out if archetypes did not change let system_meta = &mut self.system_task_metadata[system_index]; @@ -243,8 +286,8 @@ impl MultiThreadedExecutor { return false; } - for set_idx in schedule.sets_of_systems[system_index].difference(&self.evaluated_sets) { - for condition in schedule.set_conditions[set_idx].borrow_mut().iter_mut() { + 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() @@ -255,10 +298,7 @@ impl MultiThreadedExecutor { } } - for condition in schedule.system_conditions[system_index] - .borrow_mut() - .iter_mut() - { + for condition in &mut conditions.system_conditions[system_index] { condition.update_archetype_component_access(world); if !condition .archetype_component_access() @@ -269,7 +309,8 @@ impl MultiThreadedExecutor { } if !self.skipped_systems.contains(system_index) { - let mut system = schedule.systems[system_index].borrow_mut(); + // SAFETY: this system is not running, no other reference exists + let system = unsafe { &mut *systems[system_index].get() }; system.update_archetype_component_access(world); if !system .archetype_component_access() @@ -288,29 +329,29 @@ impl MultiThreadedExecutor { fn should_run( &mut self, system_index: usize, - schedule: &SystemSchedule, + #[allow(unused_variables)] systems: &[SyncUnsafeCell], + conditions: &mut Conditions, world: &World, ) -> bool { #[cfg(feature = "trace")] - let name = schedule.systems[system_index].borrow().name(); + // SAFETY: this system is not running, no other reference exists + let system = unsafe { &*systems[system_index].get() }; #[cfg(feature = "trace")] - let _span = info_span!("check_conditions", name = &*name).entered(); + let _span = info_span!("check_conditions", name = &*system.name()).entered(); let mut should_run = !self.skipped_systems.contains(system_index); - for set_idx in schedule.sets_of_systems[system_index].ones() { + 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( - schedule.set_conditions[set_idx].borrow_mut().as_mut(), - world, - ); + let set_conditions_met = + evaluate_and_fold_conditions(&mut conditions.set_conditions[set_idx], world); if !set_conditions_met { self.skipped_systems - .union_with(&schedule.systems_in_sets[set_idx]); + .union_with(&conditions.systems_in_sets[set_idx]); } should_run &= set_conditions_met; @@ -318,12 +359,8 @@ impl MultiThreadedExecutor { } // evaluate system's conditions - let system_conditions_met = evaluate_and_fold_conditions( - schedule.system_conditions[system_index] - .borrow_mut() - .as_mut(), - world, - ); + 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); @@ -338,11 +375,11 @@ impl MultiThreadedExecutor { &mut self, scope: &Scope<'_, 'scope, ()>, system_index: usize, - schedule: &'scope SystemSchedule, + systems: &'scope [SyncUnsafeCell], world: &'scope World, ) { - // SAFETY: system was not already running - let system = unsafe { &mut *schedule.systems[system_index].as_ptr() }; + // 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()); @@ -382,11 +419,11 @@ impl MultiThreadedExecutor { &mut self, scope: &Scope<'_, 'scope, ()>, system_index: usize, - schedule: &'scope SystemSchedule, + systems: &'scope [SyncUnsafeCell], world: &'scope mut World, ) { - // SAFETY: system was not already running - let system = unsafe { &mut *schedule.systems[system_index].as_ptr() }; + // 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()); @@ -400,7 +437,7 @@ impl MultiThreadedExecutor { let task = async move { #[cfg(feature = "trace")] let system_guard = system_span.enter(); - Self::apply_system_buffers(&mut unapplied_systems, schedule, world); + apply_system_buffers(&mut unapplied_systems, systems, world); #[cfg(feature = "trace")] drop(system_guard); sender @@ -475,21 +512,22 @@ impl MultiThreadedExecutor { .extend(&system_meta.archetype_component_access); } } +} - fn apply_system_buffers( - unapplied_systems: &mut FixedBitSet, - schedule: &SystemSchedule, - world: &mut World, - ) { - for system_index in unapplied_systems.ones() { - let mut system = schedule.systems[system_index].borrow_mut(); - #[cfg(feature = "trace")] - let _apply_buffers_span = info_span!("apply_buffers", name = &*system.name()).entered(); - system.apply_buffers(world); - } - - unapplied_systems.clear(); +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 { diff --git a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs index ac24989f79d98..1d45aa29129b3 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs @@ -32,7 +32,7 @@ impl SystemExecutor for SimpleExecutor { 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].get_mut().name(); + let name = schedule.systems[system_index].name(); #[cfg(feature = "trace")] let should_run_span = info_span!("check_conditions", name = &*name).entered(); @@ -43,10 +43,8 @@ impl SystemExecutor for SimpleExecutor { } // evaluate system set's conditions - let set_conditions_met = evaluate_and_fold_conditions( - schedule.set_conditions[set_idx].get_mut().as_mut(), - world, - ); + let set_conditions_met = + evaluate_and_fold_conditions(&mut schedule.set_conditions[set_idx], world); if !set_conditions_met { self.completed_systems @@ -58,10 +56,8 @@ impl SystemExecutor for SimpleExecutor { } // evaluate system's conditions - let system_conditions_met = evaluate_and_fold_conditions( - schedule.system_conditions[system_index].get_mut().as_mut(), - world, - ); + let system_conditions_met = + evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world); should_run &= system_conditions_met; @@ -75,7 +71,7 @@ impl SystemExecutor for SimpleExecutor { continue; } - let system = schedule.systems[system_index].get_mut(); + let system = &mut schedule.systems[system_index]; #[cfg(feature = "trace")] let system_span = info_span!("system", name = &*name).entered(); system.run((), world); diff --git a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs index 9d8cd3ebb083b..0b0e3f937712a 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs @@ -40,7 +40,7 @@ impl SystemExecutor for SingleThreadedExecutor { 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].get_mut().name(); + let name = schedule.systems[system_index].name(); #[cfg(feature = "trace")] let should_run_span = info_span!("check_conditions", name = &*name).entered(); @@ -51,10 +51,8 @@ impl SystemExecutor for SingleThreadedExecutor { } // evaluate system set's conditions - let set_conditions_met = evaluate_and_fold_conditions( - schedule.set_conditions[set_idx].get_mut().as_mut(), - world, - ); + let set_conditions_met = + evaluate_and_fold_conditions(&mut schedule.set_conditions[set_idx], world); if !set_conditions_met { self.completed_systems @@ -66,10 +64,8 @@ impl SystemExecutor for SingleThreadedExecutor { } // evaluate system's conditions - let system_conditions_met = evaluate_and_fold_conditions( - schedule.system_conditions[system_index].get_mut().as_mut(), - world, - ); + let system_conditions_met = + evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world); should_run &= system_conditions_met; @@ -83,7 +79,7 @@ impl SystemExecutor for SingleThreadedExecutor { continue; } - let system = schedule.systems[system_index].get_mut(); + 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(); @@ -117,7 +113,7 @@ impl SingleThreadedExecutor { fn apply_system_buffers(&mut self, schedule: &mut SystemSchedule, world: &mut World) { for system_index in self.unapplied_systems.ones() { - let system = schedule.systems[system_index].get_mut(); + 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); diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index 16805593fa356..5f6987257650a 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -1,5 +1,4 @@ use std::{ - cell::RefCell, fmt::{Debug, Write}, result::Result, }; @@ -200,17 +199,17 @@ impl Schedule { /// 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.borrow_mut().check_change_tick(change_tick); + system.check_change_tick(change_tick); } for conditions in &mut self.executable.system_conditions { - for system in conditions.borrow_mut().iter_mut() { + for system in conditions.iter_mut() { system.check_change_tick(change_tick); } } for conditions in &mut self.executable.set_conditions { - for system in conditions.borrow_mut().iter_mut() { + for system in conditions.iter_mut() { system.check_change_tick(change_tick); } } @@ -878,8 +877,8 @@ impl ScheduleGraph { .zip(schedule.systems.drain(..)) .zip(schedule.system_conditions.drain(..)) { - self.systems[id.index()] = Some(system.into_inner()); - self.system_conditions[id.index()] = Some(conditions.into_inner()); + self.systems[id.index()] = Some(system); + self.system_conditions[id.index()] = Some(conditions); } for (id, conditions) in schedule @@ -887,7 +886,7 @@ impl ScheduleGraph { .drain(..) .zip(schedule.set_conditions.drain(..)) { - self.system_set_conditions[id.index()] = Some(conditions.into_inner()); + self.system_set_conditions[id.index()] = Some(conditions); } *schedule = self.build_schedule()?; @@ -896,13 +895,13 @@ impl ScheduleGraph { 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(RefCell::new(system)); - schedule.system_conditions.push(RefCell::new(conditions)); + 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(RefCell::new(conditions)); + schedule.set_conditions.push(conditions); } Ok(()) diff --git a/crates/bevy_utils/src/syncunsafecell.rs b/crates/bevy_utils/src/syncunsafecell.rs index b610764302a35..d892bf45882dd 100644 --- a/crates/bevy_utils/src/syncunsafecell.rs +++ b/crates/bevy_utils/src/syncunsafecell.rs @@ -83,6 +83,14 @@ impl SyncUnsafeCell { } } +impl SyncUnsafeCell<[T]> { + /// Returns a `&[SyncUnsafeCell]` from a `&SyncUnsafeCell<[T]>`. + pub fn as_slice_of_cells(&self) -> &[SyncUnsafeCell] { + // SAFETY: `SyncUnsafeCell` has the same memory layout as `T`. + unsafe { &*(self as *const SyncUnsafeCell<[T]> as *const [SyncUnsafeCell]) } + } +} + impl Default for SyncUnsafeCell { /// Creates an `SyncUnsafeCell`, with the `Default` value for T. fn default() -> SyncUnsafeCell { From 3e10dc0662b27fb38149229a9a8790ddd06cc975 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sat, 14 Jan 2023 22:06:04 -0800 Subject: [PATCH 46/64] avoid counting bits in bitset --- .../schedule_v3/executor/multi_threaded.rs | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index 57af979b2000c..aaaad132dfad4 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -65,14 +65,18 @@ pub struct MultiThreadedExecutor { receiver: Receiver, /// Metadata for scheduling and running system tasks. system_task_metadata: Vec, - /// The number of dependencies each system has that have not completed. - dependencies_remaining: 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. @@ -120,16 +124,19 @@ impl SystemExecutor for MultiThreadedExecutor { }); } - self.dependencies_remaining = Vec::with_capacity(sys_count); + self.num_dependencies_remaining = Vec::with_capacity(sys_count); } fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) { // reset counts - self.dependencies_remaining.clear(); - self.dependencies_remaining + 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.dependencies_remaining.iter_mut().enumerate() { + for (system_index, dependencies) in self.num_dependencies_remaining.iter_mut().enumerate() { if *dependencies == 0 { self.ready_systems.insert(system_index); } @@ -148,7 +155,7 @@ impl SystemExecutor for MultiThreadedExecutor { // the executor itself is a `Send` future so that it can run // alongside systems that claim the local thread let executor = async { - while self.completed_systems.count_ones(..) != self.completed_systems.len() { + while self.num_completed_systems < num_systems { if !self.exclusive_running { ready_systems.clear(); ready_systems.union_with(&self.ready_systems); @@ -161,7 +168,7 @@ impl SystemExecutor for MultiThreadedExecutor { ); } - if !self.running_systems.is_clear() { + if self.num_running_systems > 0 { // wait for systems to complete let index = self .receiver @@ -208,7 +215,9 @@ impl MultiThreadedExecutor { sender, receiver, system_task_metadata: Vec::new(), - dependencies_remaining: 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, @@ -250,6 +259,7 @@ impl MultiThreadedExecutor { // system is starting self.running_systems.insert(system_index); + self.num_running_systems += 1; if self.system_task_metadata[system_index].is_exclusive { // SAFETY: `can_run` confirmed no other systems are running @@ -278,7 +288,7 @@ impl MultiThreadedExecutor { // TODO: an earlier out if archetypes did not change let system_meta = &mut self.system_task_metadata[system_index]; - if system_meta.is_exclusive && !self.running_systems.is_clear() { + if system_meta.is_exclusive && self.num_running_systems > 0 { return false; } @@ -480,6 +490,9 @@ impl MultiThreadedExecutor { self.exclusive_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); @@ -487,6 +500,7 @@ impl MultiThreadedExecutor { } 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); } @@ -495,10 +509,10 @@ impl MultiThreadedExecutor { #[cfg(feature = "trace")] let _span = info_span!("signal_dependents").entered(); for &dep_idx in &self.system_task_metadata[system_index].dependents { - let dependencies = &mut self.dependencies_remaining[dep_idx]; - debug_assert!(*dependencies >= 1); - *dependencies -= 1; - if *dependencies == 0 && !self.completed_systems.contains(dep_idx) { + 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); } } From ca4e6438545a49ad9adfbd7104de614de9b1f16b Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sat, 14 Jan 2023 22:20:50 -0800 Subject: [PATCH 47/64] style --- .../schedule_v3/executor/multi_threaded.rs | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index aaaad132dfad4..adfe6aa7ae076 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -239,9 +239,12 @@ impl MultiThreadedExecutor { ready_systems: &FixedBitSet, ) { for system_index in ready_systems.ones() { + // SAFETY: this system is not running, no other reference exists + let system = unsafe { &mut *systems[system_index].get() }; + // SAFETY: no exclusive system is running let world = unsafe { &*cell.get() }; - if !self.can_run(system_index, systems, conditions, world) { + 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 @@ -252,7 +255,7 @@ impl MultiThreadedExecutor { // system is either going to run or be skipped self.ready_systems.set(system_index, false); - if !self.should_run(system_index, systems, conditions, world) { + if !self.should_run(system_index, system, conditions, world) { self.skip_system_and_signal_dependents(system_index); continue; } @@ -275,19 +278,14 @@ impl MultiThreadedExecutor { fn can_run( &mut self, system_index: usize, - systems: &[SyncUnsafeCell], + system: &mut BoxedSystem, conditions: &mut Conditions, world: &World, ) -> bool { - #[cfg(feature = "trace")] - // SAFETY: this system is not running, no other reference exists - let system = unsafe { &*systems[system_index].get() }; #[cfg(feature = "trace")] let _span = info_span!("check_access", name = &*system.name()).entered(); - // TODO: an earlier out if archetypes did not change - let system_meta = &mut self.system_task_metadata[system_index]; - + let system_meta = &self.system_task_metadata[system_index]; if system_meta.is_exclusive && self.num_running_systems > 0 { return false; } @@ -296,6 +294,7 @@ impl MultiThreadedExecutor { 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); @@ -319,8 +318,6 @@ impl MultiThreadedExecutor { } if !self.skipped_systems.contains(system_index) { - // SAFETY: this system is not running, no other reference exists - let system = unsafe { &mut *systems[system_index].get() }; system.update_archetype_component_access(world); if !system .archetype_component_access() @@ -329,8 +326,9 @@ impl MultiThreadedExecutor { return false; } - // TODO: avoid allocation by keeping count of readers - system_meta.archetype_component_access = system.archetype_component_access().clone(); + // TODO: avoid allocation + self.system_task_metadata[system_index].archetype_component_access = + system.archetype_component_access().clone(); } true @@ -339,13 +337,10 @@ impl MultiThreadedExecutor { fn should_run( &mut self, system_index: usize, - #[allow(unused_variables)] systems: &[SyncUnsafeCell], + #[allow(unused_variables)] system: &BoxedSystem, conditions: &mut Conditions, world: &World, ) -> bool { - #[cfg(feature = "trace")] - // SAFETY: this system is not running, no other reference exists - let system = unsafe { &*systems[system_index].get() }; #[cfg(feature = "trace")] let _span = info_span!("check_conditions", name = &*system.name()).entered(); @@ -477,19 +472,19 @@ impl MultiThreadedExecutor { scope.spawn_on_scope(task); } - self.local_thread_running = true; 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_send { - self.local_thread_running = false; - } - 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; From e357e3084e3c10e89d7befe2d1c7f951a94e780f Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 15 Jan 2023 10:44:19 -0800 Subject: [PATCH 48/64] Apply suggestions from review --- crates/bevy_ecs/src/schedule_v3/config.rs | 123 +++++++++--------- .../schedule_v3/executor/multi_threaded.rs | 57 ++++---- .../src/schedule_v3/executor/simple.rs | 10 +- .../schedule_v3/executor/single_threaded.rs | 12 +- crates/bevy_ecs/src/world/mod.rs | 26 ++-- crates/bevy_utils/src/syncunsafecell.rs | 19 ++- 6 files changed, 137 insertions(+), 110 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/config.rs b/crates/bevy_ecs/src/schedule_v3/config.rs index 509a66db52e0c..39ebb232a1e4b 100644 --- a/crates/bevy_ecs/src/schedule_v3/config.rs +++ b/crates/bevy_ecs/src/schedule_v3/config.rs @@ -16,6 +16,27 @@ pub struct SystemSetConfig { 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, @@ -23,39 +44,19 @@ pub struct SystemConfig { pub(super) conditions: Vec, } -pub(super) fn new_set_unchecked(set: BoxedSystemSet) -> SystemSetConfig { - SystemSetConfig { - set, - graph_info: GraphInfo { - sets: Vec::new(), - dependencies: Vec::new(), - ambiguous_with: default(), - }, - conditions: Vec::new(), - } -} - -fn new_set(set: BoxedSystemSet) -> SystemSetConfig { - // 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" - ); - new_set_unchecked(set) -} - -fn new_system(system: BoxedSystem) -> SystemConfig { - // include system in its default sets - let sets = system.default_system_sets().into_iter().collect(); - SystemConfig { - system, - graph_info: GraphInfo { - sets, - dependencies: Vec::new(), - ambiguous_with: default(), - }, - conditions: Vec::new(), +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(), + } } } @@ -101,61 +102,61 @@ where S: SystemSet + sealed::IntoSystemSetConfig, { fn into_config(self) -> SystemSetConfig { - new_set(Box::new(self)) + SystemSetConfig::new(Box::new(self)) } fn in_set(self, set: impl SystemSet) -> SystemSetConfig { - new_set(Box::new(self)).in_set(set) + SystemSetConfig::new(Box::new(self)).in_set(set) } fn before(self, set: impl IntoSystemSet) -> SystemSetConfig { - new_set(Box::new(self)).before(set) + SystemSetConfig::new(Box::new(self)).before(set) } fn after(self, set: impl IntoSystemSet) -> SystemSetConfig { - new_set(Box::new(self)).after(set) + SystemSetConfig::new(Box::new(self)).after(set) } fn run_if

(self, condition: impl Condition

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

(self, condition: impl Condition

) -> SystemSetConfig { - new_set(self).run_if(condition) + SystemSetConfig::new(self).run_if(condition) } fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemSetConfig { - new_set(self).ambiguous_with(set) + SystemSetConfig::new(self).ambiguous_with(set) } fn ambiguous_with_all(self) -> SystemSetConfig { - new_set(self).ambiguous_with_all() + SystemSetConfig::new(self).ambiguous_with_all() } } @@ -248,61 +249,61 @@ where F: IntoSystem<(), (), Params> + sealed::IntoSystemConfig, { fn into_config(self) -> SystemConfig { - new_system(Box::new(IntoSystem::into_system(self))) + SystemConfig::new(Box::new(IntoSystem::into_system(self))) } fn in_set(self, set: impl SystemSet) -> SystemConfig { - new_system(Box::new(IntoSystem::into_system(self))).in_set(set) + SystemConfig::new(Box::new(IntoSystem::into_system(self))).in_set(set) } fn before(self, set: impl IntoSystemSet) -> SystemConfig { - new_system(Box::new(IntoSystem::into_system(self))).before(set) + SystemConfig::new(Box::new(IntoSystem::into_system(self))).before(set) } fn after(self, set: impl IntoSystemSet) -> SystemConfig { - new_system(Box::new(IntoSystem::into_system(self))).after(set) + SystemConfig::new(Box::new(IntoSystem::into_system(self))).after(set) } fn run_if

(self, condition: impl Condition

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

(self, condition: impl Condition

) -> SystemConfig { - new_system(self).run_if(condition) + SystemConfig::new(self).run_if(condition) } fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemConfig { - new_system(self).ambiguous_with(set) + SystemConfig::new(self).ambiguous_with(set) } fn ambiguous_with_all(self) -> SystemConfig { - new_system(self).ambiguous_with_all() + SystemConfig::new(self).ambiguous_with_all() } } diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index adfe6aa7ae076..3d41bf4acc05c 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -143,7 +143,7 @@ impl SystemExecutor for MultiThreadedExecutor { } // using spare bitset to avoid repeated allocations - let mut ready_systems = FixedBitSet::with_capacity(self.ready_systems.len()); + let mut cached_ready_systems = FixedBitSet::with_capacity(self.ready_systems.len()); let world = SyncUnsafeCell::from_mut(world); let SyncUnsafeSchedule { @@ -156,17 +156,13 @@ impl SystemExecutor for MultiThreadedExecutor { // alongside systems that claim the local thread let executor = async { while self.num_completed_systems < num_systems { - if !self.exclusive_running { - ready_systems.clear(); - ready_systems.union_with(&self.ready_systems); - self.spawn_system_tasks( - scope, - systems, - &mut conditions, - world, - &ready_systems, - ); - } + self.spawn_system_tasks( + scope, + systems, + &mut conditions, + world, + &mut cached_ready_systems, + ); if self.num_running_systems > 0 { // wait for systems to complete @@ -236,9 +232,17 @@ impl MultiThreadedExecutor { systems: &'scope [SyncUnsafeCell], conditions: &mut Conditions, cell: &'scope SyncUnsafeCell, - ready_systems: &FixedBitSet, + ready_systems: &mut FixedBitSet, ) { + if self.exclusive_running { + return; + } + + 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: this system is not running, no other reference exists let system = unsafe { &mut *systems[system_index].get() }; @@ -266,12 +270,17 @@ impl MultiThreadedExecutor { if self.system_task_metadata[system_index].is_exclusive { // SAFETY: `can_run` confirmed no other systems are running - let world = unsafe { &mut *cell.get() }; - self.spawn_exclusive_system_task(scope, system_index, systems, world); + unsafe { + let world = &mut *cell.get(); + self.spawn_exclusive_system_task(scope, system_index, systems, world); + } break; } - self.spawn_system_task(scope, system_index, systems, world); + // SAFETY: this system is not running, no other reference exists + unsafe { + self.spawn_system_task(scope, system_index, systems, world); + } } } @@ -337,15 +346,16 @@ impl MultiThreadedExecutor { fn should_run( &mut self, system_index: usize, - #[allow(unused_variables)] system: &BoxedSystem, + _system: &BoxedSystem, conditions: &mut Conditions, world: &World, ) -> bool { #[cfg(feature = "trace")] - let _span = info_span!("check_conditions", name = &*system.name()).entered(); + 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() { + let sets_of_system = &conditions.sets_of_systems[system_index]; + for set_idx in sets_of_system.difference(&self.evaluated_sets) { if self.evaluated_sets.contains(set_idx) { continue; } @@ -360,9 +370,10 @@ impl MultiThreadedExecutor { } should_run &= set_conditions_met; - self.evaluated_sets.insert(set_idx); } + self.evaluated_sets.union_with(sets_of_system); + // evaluate system's conditions let system_conditions_met = evaluate_and_fold_conditions(&mut conditions.system_conditions[system_index], world); @@ -376,7 +387,8 @@ impl MultiThreadedExecutor { should_run } - fn spawn_system_task<'scope>( + // SAFETY: caller must not alias systems that are running (those are already mutably borrowed) + unsafe fn spawn_system_task<'scope>( &mut self, scope: &Scope<'_, 'scope, ()>, system_index: usize, @@ -420,7 +432,8 @@ impl MultiThreadedExecutor { } } - fn spawn_exclusive_system_task<'scope>( + // SAFETY: caller must ensure that no systems are running + unsafe fn spawn_exclusive_system_task<'scope>( &mut self, scope: &Scope<'_, 'scope, ()>, system_index: usize, diff --git a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs index 1d45aa29129b3..1a5b79f92085d 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs @@ -37,11 +37,8 @@ impl SystemExecutor for SimpleExecutor { 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; - } - + let sets_of_system = &schedule.sets_of_systems[system_index]; + for set_idx in sets_of_system.difference(&self.evaluated_sets) { // evaluate system set's conditions let set_conditions_met = evaluate_and_fold_conditions(&mut schedule.set_conditions[set_idx], world); @@ -52,9 +49,10 @@ impl SystemExecutor for SimpleExecutor { } should_run &= set_conditions_met; - self.evaluated_sets.insert(set_idx); } + self.evaluated_sets.union_with(sets_of_system); + // evaluate system's conditions let system_conditions_met = evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world); diff --git a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs index 0b0e3f937712a..7bf90cb6631ff 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs @@ -45,11 +45,8 @@ impl SystemExecutor for SingleThreadedExecutor { 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; - } - + let sets_of_system = &schedule.sets_of_systems[system_index]; + for set_idx in sets_of_system.difference(&self.evaluated_sets) { // evaluate system set's conditions let set_conditions_met = evaluate_and_fold_conditions(&mut schedule.set_conditions[set_idx], world); @@ -60,9 +57,10 @@ impl SystemExecutor for SingleThreadedExecutor { } should_run &= set_conditions_met; - self.evaluated_sets.insert(set_idx); } + self.evaluated_sets.union_with(sets_of_system); + // evaluate system's conditions let system_conditions_met = evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world); @@ -92,7 +90,7 @@ impl SystemExecutor for SingleThreadedExecutor { system.run((), world); #[cfg(feature = "trace")] system_span.exit(); - self.unapplied_systems.set(system_index, true); + self.unapplied_systems.insert(system_index); } } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 3ddeba0a052e0..c267be6bac432 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1594,6 +1594,10 @@ impl World { pub fn check_change_ticks(&mut self) { let last_check_tick = self.last_check_tick; let change_tick = self.change_tick(); + if change_tick.wrapping_sub(last_check_tick) < CHECK_TICK_THRESHOLD { + return; + } + let Storages { ref mut tables, ref mut sparse_sets, @@ -1601,20 +1605,18 @@ impl World { ref mut non_send_resources, } = self.storages; - if change_tick.wrapping_sub(last_check_tick) >= CHECK_TICK_THRESHOLD { - #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!("check component ticks").entered(); - tables.check_change_ticks(change_tick); - sparse_sets.check_change_ticks(change_tick); - resources.check_change_ticks(change_tick); - non_send_resources.check_change_ticks(change_tick); + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!("check component ticks").entered(); + tables.check_change_ticks(change_tick); + sparse_sets.check_change_ticks(change_tick); + resources.check_change_ticks(change_tick); + non_send_resources.check_change_ticks(change_tick); - if let Some(mut schedules) = self.get_resource_mut::() { - schedules.check_change_ticks(change_tick); - } - - self.last_check_tick = change_tick; + if let Some(mut schedules) = self.get_resource_mut::() { + schedules.check_change_ticks(change_tick); } + + self.last_check_tick = change_tick; } pub fn clear_entities(&mut self) { diff --git a/crates/bevy_utils/src/syncunsafecell.rs b/crates/bevy_utils/src/syncunsafecell.rs index d892bf45882dd..102d493ab4e1f 100644 --- a/crates/bevy_utils/src/syncunsafecell.rs +++ b/crates/bevy_utils/src/syncunsafecell.rs @@ -78,15 +78,30 @@ impl SyncUnsafeCell { #[inline] /// Returns a `&SyncUnsafeCell` from a `&mut T`. pub fn from_mut(t: &mut T) -> &SyncUnsafeCell { - // SAFETY: `&mut` ensures unique access. + // SAFETY: `&mut` ensures unique access, and `UnsafeCell` and `SyncUnsafeCell` + // have #[repr(transparent)] unsafe { &*(t as *mut T as *const SyncUnsafeCell) } } } impl SyncUnsafeCell<[T]> { /// Returns a `&[SyncUnsafeCell]` from a `&SyncUnsafeCell<[T]>`. + /// # Examples + /// + /// ``` + /// # use bevy_utils::syncunsafecell::SyncUnsafeCell; + /// + /// let slice: &mut [i32] = &mut [1, 2, 3]; + /// let cell_slice: &SyncUnsafeCell<[i32]> = SyncUnsafeCell::from_mut(slice); + /// let slice_cell: &[SyncUnsafeCell] = cell_slice.as_slice_of_cells(); + /// + /// assert_eq!(slice_cell.len(), 3); pub fn as_slice_of_cells(&self) -> &[SyncUnsafeCell] { - // SAFETY: `SyncUnsafeCell` has the same memory layout as `T`. + // SAFETY: `UnsafeCell` and `SyncUnsafeCell` have #[repr(transparent)] + // therefore: + // - `SyncUnsafeCell` has the same layout as `T` + // - `SyncUnsafeCell<[T]>` has the same layout as `[T]` + // - `SyncUnsafeCell<[T]>` has the same layout as `[SyncUnsafeCell]` unsafe { &*(self as *const SyncUnsafeCell<[T]> as *const [SyncUnsafeCell]) } } } From 66263c78c50d287863d1cee521ebda348cda9f44 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 15 Jan 2023 10:51:20 -0800 Subject: [PATCH 49/64] rename `Statelike` to `States` and remove blanket impl --- crates/bevy_ecs/src/schedule_v3/condition.rs | 8 ++++---- crates/bevy_ecs/src/schedule_v3/migration.rs | 2 +- crates/bevy_ecs/src/schedule_v3/state.rs | 20 ++++++++++++-------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/condition.rs b/crates/bevy_ecs/src/schedule_v3/condition.rs index f48a776d227de..32f3994e7968f 100644 --- a/crates/bevy_ecs/src/schedule_v3/condition.rs +++ b/crates/bevy_ecs/src/schedule_v3/condition.rs @@ -29,7 +29,7 @@ mod sealed { } mod common_conditions { - use crate::schedule_v3::{State, Statelike}; + use crate::schedule_v3::{State, States}; use crate::system::{Res, Resource}; /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` @@ -70,7 +70,7 @@ mod common_conditions { /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if the state machine exists. - pub fn state_exists() -> impl FnMut(Option>>) -> bool { + pub fn state_exists() -> impl FnMut(Option>>) -> bool { move |current_state: Option>>| current_state.is_some() } @@ -80,7 +80,7 @@ mod common_conditions { /// # Panics /// /// The condition will panic if the resource does not exist. - pub fn state_equals(state: S) -> impl FnMut(Res>) -> bool { + pub fn state_equals(state: S) -> impl FnMut(Res>) -> bool { move |current_state: Res>| current_state.0 == state } @@ -88,7 +88,7 @@ mod common_conditions { /// 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( + pub fn state_exists_and_equals( state: S, ) -> impl FnMut(Option>>) -> bool { move |current_state: Option>>| match current_state { diff --git a/crates/bevy_ecs/src/schedule_v3/migration.rs b/crates/bevy_ecs/src/schedule_v3/migration.rs index 967d6bdb7a85f..c4932c2fbca7c 100644 --- a/crates/bevy_ecs/src/schedule_v3/migration.rs +++ b/crates/bevy_ecs/src/schedule_v3/migration.rs @@ -19,7 +19,7 @@ pub trait AppExt { /// 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; + fn add_state(&mut self) -> &mut Self; } /// Temporary "stageless" [`World`] methods. diff --git a/crates/bevy_ecs/src/schedule_v3/state.rs b/crates/bevy_ecs/src/schedule_v3/state.rs index bd3240c43b7df..85f357c670f7f 100644 --- a/crates/bevy_ecs/src/schedule_v3/state.rs +++ b/crates/bevy_ecs/src/schedule_v3/state.rs @@ -8,25 +8,29 @@ use crate::system::Resource; use crate::world::World; /// Types that can define states in a finite-state machine. -pub trait Statelike: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug {} -impl Statelike for T where T: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug {} +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); +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); +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); +pub struct OnUpdate(pub S); /// A finite-state machine whose transitions have associated schedules /// ([`OnEnter(state)`] and [`OnExit(state)`]). @@ -35,19 +39,19 @@ pub struct OnUpdate(pub S); /// 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); +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); +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) { +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( From 1441bcdad4277a5bbe96cefde4a99bc847273df9 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 15 Jan 2023 11:04:21 -0800 Subject: [PATCH 50/64] all_tuples --- crates/bevy_ecs/src/schedule_v3/config.rs | 59 +++-------------------- 1 file changed, 6 insertions(+), 53 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/config.rs b/crates/bevy_ecs/src/schedule_v3/config.rs index 39ebb232a1e4b..9475f8efe684a 100644 --- a/crates/bevy_ecs/src/schedule_v3/config.rs +++ b/crates/bevy_ecs/src/schedule_v3/config.rs @@ -1,3 +1,4 @@ +use bevy_ecs_macros::all_tuples; use bevy_utils::default; use crate::{ @@ -563,8 +564,8 @@ impl IntoSystemSetConfigs for SystemSetConfigs { } macro_rules! impl_system_collection { - ($($param: ident, $sys: ident),*) => { - impl<$($param, $sys),*> IntoSystemConfigs<($($param),*)> for ($($sys),*) + ($(($param: ident, $sys: ident)),*) => { + impl<$($param, $sys),*> IntoSystemConfigs<($($param,)*)> for ($($sys,)*) where $($sys: IntoSystemConfig<$param>),* { @@ -582,7 +583,7 @@ macro_rules! impl_system_collection { macro_rules! impl_system_set_collection { ($($set: ident),*) => { - impl<$($set: IntoSystemSetConfig),*> IntoSystemSetConfigs for ($($set),*) + impl<$($set: IntoSystemSetConfig),*> IntoSystemSetConfigs for ($($set,)*) { #[allow(non_snake_case)] fn into_configs(self) -> SystemSetConfigs { @@ -596,53 +597,5 @@ macro_rules! impl_system_set_collection { } } -impl_system_collection!(P0, T0, P1, T1); -impl_system_collection!(P0, T0, P1, T1, P2, T2); -impl_system_collection!(P0, T0, P1, T1, P2, T2, P3, T3); -impl_system_collection!(P0, T0, P1, T1, P2, T2, P3, T3, P4, T4); -impl_system_collection!(P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5); -impl_system_collection!(P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6); -impl_system_collection!(P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7); -impl_system_collection!(P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8); -impl_system_collection!( - P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9 -); -impl_system_collection!( - P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10 -); -impl_system_collection!( - P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10, P11, - T11 -); -impl_system_collection!( - P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10, P11, - T11, P12, T12 -); -impl_system_collection!( - P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10, P11, - T11, P12, T12, P13, T13 -); -impl_system_collection!( - P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10, P11, - T11, P12, T12, P13, T13, P14, T14 -); -impl_system_collection!( - P0, T0, P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10, P11, - T11, P12, T12, P13, T13, P14, T14, P15, T15 -); - -impl_system_set_collection!(S0, S1); -impl_system_set_collection!(S0, S1, S2); -impl_system_set_collection!(S0, S1, S2, S3); -impl_system_set_collection!(S0, S1, S2, S3, S4); -impl_system_set_collection!(S0, S1, S2, S3, S4, S5); -impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6); -impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6, S7); -impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6, S7, S8); -impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6, S7, S8, S9); -impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10); -impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11); -impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12); -impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13); -impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14); -impl_system_set_collection!(S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14, S15); +all_tuples!(impl_system_collection, 0, 15, P, S); +all_tuples!(impl_system_set_collection, 0, 15, S); From 8d65fac35024a69594661385f6c16a23a4583552 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 15 Jan 2023 11:29:54 -0800 Subject: [PATCH 51/64] remove TODO --- crates/bevy_ecs/src/schedule_v3/schedule.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index 5f6987257650a..135ff9341bee6 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -937,7 +937,6 @@ impl ScheduleGraph { } fn report_hierarchy_conflicts(&self, transitive_edges: &[(NodeId, NodeId)]) { - // TODO: warn but fix with transitive reduction let mut message = String::from("hierarchy contains redundant edge(s)"); for (parent, child) in transitive_edges { writeln!( From 7c0a1dae10294dd5b7114c572172b8c78347e452 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 15 Jan 2023 12:14:16 -0800 Subject: [PATCH 52/64] comment --- crates/bevy_ecs/src/schedule_v3/executor/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs index f69675e91ad59..bfc1eef14d609 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/mod.rs @@ -41,6 +41,9 @@ pub enum ExecutorKind { /// 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, From 4dc4139ddf5aee1f73601371c281b108758e1f42 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 15 Jan 2023 12:32:16 -0800 Subject: [PATCH 53/64] Apply suggestions from code review Co-authored-by: James Liu --- crates/bevy_ecs/src/world/mod.rs | 3 +-- crates/bevy_utils/src/syncunsafecell.rs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index c267be6bac432..cfbaa334987a9 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1592,9 +1592,8 @@ impl World { /// times since the previous pass. // TODO: benchmark and optimize pub fn check_change_ticks(&mut self) { - let last_check_tick = self.last_check_tick; let change_tick = self.change_tick(); - if change_tick.wrapping_sub(last_check_tick) < CHECK_TICK_THRESHOLD { + if change_tick.wrapping_sub(self.last_check_tick) < CHECK_TICK_THRESHOLD { return; } diff --git a/crates/bevy_utils/src/syncunsafecell.rs b/crates/bevy_utils/src/syncunsafecell.rs index 102d493ab4e1f..07e2422d89a8a 100644 --- a/crates/bevy_utils/src/syncunsafecell.rs +++ b/crates/bevy_utils/src/syncunsafecell.rs @@ -96,6 +96,7 @@ impl SyncUnsafeCell<[T]> { /// let slice_cell: &[SyncUnsafeCell] = cell_slice.as_slice_of_cells(); /// /// assert_eq!(slice_cell.len(), 3); + /// ``` pub fn as_slice_of_cells(&self) -> &[SyncUnsafeCell] { // SAFETY: `UnsafeCell` and `SyncUnsafeCell` have #[repr(transparent)] // therefore: From d8516165e538fe8e9f2a76d1779963faf418bec1 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 15 Jan 2023 12:33:18 -0800 Subject: [PATCH 54/64] simplify trait bound --- crates/bevy_ecs/src/schedule_v3/condition.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/condition.rs b/crates/bevy_ecs/src/schedule_v3/condition.rs index 32f3994e7968f..e617375f763a0 100644 --- a/crates/bevy_ecs/src/schedule_v3/condition.rs +++ b/crates/bevy_ecs/src/schedule_v3/condition.rs @@ -13,16 +13,14 @@ pub trait Condition: sealed::Condition {} impl Condition for F where F: sealed::Condition {} mod sealed { - use crate::system::{ - IntoSystem, IsFunctionSystem, ReadOnlySystemParam, SystemParam, SystemParamFunction, - }; + 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: SystemParam + ReadOnlySystemParam + 'static, + Params: ReadOnlySystemParam + 'static, Marker: 'static, { } From fbc2438a5980fbacfeda66920b38525e31bab9f3 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 15 Jan 2023 14:42:09 -0800 Subject: [PATCH 55/64] james said `difference` isn't optimized --- .../src/schedule_v3/executor/multi_threaded.rs | 6 ++---- crates/bevy_ecs/src/schedule_v3/executor/simple.rs | 10 ++++++---- .../src/schedule_v3/executor/single_threaded.rs | 10 ++++++---- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index 3d41bf4acc05c..1dcebe4fb4872 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -354,8 +354,7 @@ impl MultiThreadedExecutor { let _span = info_span!("check_conditions", name = &*_system.name()).entered(); let mut should_run = !self.skipped_systems.contains(system_index); - let sets_of_system = &conditions.sets_of_systems[system_index]; - for set_idx in sets_of_system.difference(&self.evaluated_sets) { + for set_idx in conditions.sets_of_systems[system_index].ones() { if self.evaluated_sets.contains(set_idx) { continue; } @@ -370,10 +369,9 @@ impl MultiThreadedExecutor { } should_run &= set_conditions_met; + self.evaluated_sets.insert(set_idx); } - self.evaluated_sets.union_with(sets_of_system); - // evaluate system's conditions let system_conditions_met = evaluate_and_fold_conditions(&mut conditions.system_conditions[system_index], world); diff --git a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs index 1a5b79f92085d..1d45aa29129b3 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/simple.rs @@ -37,8 +37,11 @@ impl SystemExecutor for SimpleExecutor { let should_run_span = info_span!("check_conditions", name = &*name).entered(); let mut should_run = !self.completed_systems.contains(system_index); - let sets_of_system = &schedule.sets_of_systems[system_index]; - for set_idx in sets_of_system.difference(&self.evaluated_sets) { + 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); @@ -49,10 +52,9 @@ impl SystemExecutor for SimpleExecutor { } should_run &= set_conditions_met; + self.evaluated_sets.insert(set_idx); } - self.evaluated_sets.union_with(sets_of_system); - // evaluate system's conditions let system_conditions_met = evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world); diff --git a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs index 7bf90cb6631ff..289b05b8c1e78 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs @@ -45,8 +45,11 @@ impl SystemExecutor for SingleThreadedExecutor { let should_run_span = info_span!("check_conditions", name = &*name).entered(); let mut should_run = !self.completed_systems.contains(system_index); - let sets_of_system = &schedule.sets_of_systems[system_index]; - for set_idx in sets_of_system.difference(&self.evaluated_sets) { + 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); @@ -57,10 +60,9 @@ impl SystemExecutor for SingleThreadedExecutor { } should_run &= set_conditions_met; + self.evaluated_sets.insert(set_idx); } - self.evaluated_sets.union_with(sets_of_system); - // evaluate system's conditions let system_conditions_met = evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world); From 7a15581400cae27a9bc8b3c26b568dc1f96561ba Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 15 Jan 2023 14:39:08 -0800 Subject: [PATCH 56/64] add `ambiguous_with` for tuples --- crates/bevy_ecs/src/schedule_v3/config.rs | 104 ++++++++++++++++------ 1 file changed, 75 insertions(+), 29 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/config.rs b/crates/bevy_ecs/src/schedule_v3/config.rs index 9475f8efe684a..d93765ee3c284 100644 --- a/crates/bevy_ecs/src/schedule_v3/config.rs +++ b/crates/bevy_ecs/src/schedule_v3/config.rs @@ -72,6 +72,20 @@ fn new_condition

(condition: impl Condition

) -> BoxedCondition { Box::new(condition_system) } +fn ambiguous_with(graph_info: &mut GraphInfo, set: BoxedSystemSet) { + match &mut graph_info.ambiguous_with { + detection @ Ambiguity::Check => { + let mut ambiguous_with = Vec::new(); + ambiguous_with.push(set); + *detection = Ambiguity::IgnoreWithSet(ambiguous_with); + } + 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. @@ -195,20 +209,7 @@ impl IntoSystemSetConfig for SystemSetConfig { } fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { - match &mut self.graph_info.ambiguous_with { - detection @ Ambiguity::Check => { - let mut ambiguous_with = Vec::new(); - let boxed: Box = Box::new(set.into_system_set()); - ambiguous_with.push(boxed); - *detection = Ambiguity::IgnoreWithSet(ambiguous_with); - } - Ambiguity::IgnoreWithSet(ambiguous_with) => { - let boxed: Box = Box::new(set.into_system_set()); - ambiguous_with.push(boxed); - } - Ambiguity::IgnoreAll => (), - } - + ambiguous_with(&mut self.graph_info, Box::new(set.into_system_set())); self } @@ -342,24 +343,11 @@ impl IntoSystemConfig<()> for SystemConfig { } fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { - match &mut self.graph_info.ambiguous_with { - detection @ Ambiguity::Check => { - let mut ambiguous_with = Vec::new(); - let boxed: Box = Box::new(set.into_system_set()); - ambiguous_with.push(boxed); - *detection = Ambiguity::IgnoreWithSet(ambiguous_with); - } - Ambiguity::IgnoreWithSet(ambiguous_with) => { - let boxed: Box = Box::new(set.into_system_set()); - ambiguous_with.push(boxed); - } - Ambiguity::IgnoreAll => (), - } - + ambiguous_with(&mut self.graph_info, Box::new(set.into_system_set())); self } - fn ambiguous_with_all(mut self) -> SystemConfig { + fn ambiguous_with_all(mut self) -> Self { self.graph_info.ambiguous_with = Ambiguity::IgnoreAll; self } @@ -422,6 +410,18 @@ where 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. @@ -471,6 +471,23 @@ impl IntoSystemConfigs<()> for SystemConfigs { 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 @@ -508,6 +525,18 @@ where 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. @@ -557,6 +586,23 @@ impl IntoSystemSetConfigs for SystemSetConfigs { 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 From 2020bd467da71f915eb01216dd1c591fe2c52a2a Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 15 Jan 2023 14:39:25 -0800 Subject: [PATCH 57/64] Apply suggestions from review --- crates/bevy_ecs/src/schedule_v3/config.rs | 38 ++++++++++--------- .../bevy_ecs/src/schedule_v3/graph_utils.rs | 19 ++++++++-- crates/bevy_ecs/src/schedule_v3/schedule.rs | 12 +++--- 3 files changed, 43 insertions(+), 26 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/config.rs b/crates/bevy_ecs/src/schedule_v3/config.rs index d93765ee3c284..676bf397f99f9 100644 --- a/crates/bevy_ecs/src/schedule_v3/config.rs +++ b/crates/bevy_ecs/src/schedule_v3/config.rs @@ -4,7 +4,7 @@ use bevy_utils::default; use crate::{ schedule_v3::{ condition::{BoxedCondition, Condition}, - graph_utils::{Ambiguity, DependencyEdgeKind, GraphInfo}, + graph_utils::{Ambiguity, Dependency, DependencyKind, GraphInfo}, set::{BoxedSystemSet, IntoSystemSet, SystemSet}, }, system::{BoxedSystem, IntoSystem, System}, @@ -190,16 +190,18 @@ impl IntoSystemSetConfig for SystemSetConfig { } fn before(mut self, set: impl IntoSystemSet) -> Self { - self.graph_info - .dependencies - .push((DependencyEdgeKind::Before, Box::new(set.into_system_set()))); + 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((DependencyEdgeKind::After, Box::new(set.into_system_set()))); + self.graph_info.dependencies.push(Dependency::new( + DependencyKind::After, + Box::new(set.into_system_set()), + )); self } @@ -324,16 +326,18 @@ impl IntoSystemConfig<()> for SystemConfig { } fn before(mut self, set: impl IntoSystemSet) -> Self { - self.graph_info - .dependencies - .push((DependencyEdgeKind::Before, Box::new(set.into_system_set()))); + 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((DependencyEdgeKind::After, Box::new(set.into_system_set()))); + self.graph_info.dependencies.push(Dependency::new( + DependencyKind::After, + Box::new(set.into_system_set()), + )); self } @@ -453,7 +457,7 @@ impl IntoSystemConfigs<()> for SystemConfigs { config .graph_info .dependencies - .push((DependencyEdgeKind::Before, set.dyn_clone())); + .push(Dependency::new(DependencyKind::Before, set.dyn_clone())); } self @@ -465,7 +469,7 @@ impl IntoSystemConfigs<()> for SystemConfigs { config .graph_info .dependencies - .push((DependencyEdgeKind::After, set.dyn_clone())); + .push(Dependency::new(DependencyKind::After, set.dyn_clone())); } self @@ -568,7 +572,7 @@ impl IntoSystemSetConfigs for SystemSetConfigs { config .graph_info .dependencies - .push((DependencyEdgeKind::Before, set.dyn_clone())); + .push(Dependency::new(DependencyKind::Before, set.dyn_clone())); } self @@ -580,7 +584,7 @@ impl IntoSystemSetConfigs for SystemSetConfigs { config .graph_info .dependencies - .push((DependencyEdgeKind::After, set.dyn_clone())); + .push(Dependency::new(DependencyKind::After, set.dyn_clone())); } self diff --git a/crates/bevy_ecs/src/schedule_v3/graph_utils.rs b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs index aedfc46ef1814..b58bad317a959 100644 --- a/crates/bevy_ecs/src/schedule_v3/graph_utils.rs +++ b/crates/bevy_ecs/src/schedule_v3/graph_utils.rs @@ -34,15 +34,28 @@ impl NodeId { } } -/// Specifies what kind of edge should be inserted in the dependency graph. +/// Specifies what kind of edge should be added to the dependency graph. #[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub(crate) enum DependencyEdgeKind { +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 { @@ -57,7 +70,7 @@ pub(crate) enum Ambiguity { #[derive(Clone)] pub(crate) struct GraphInfo { pub(crate) sets: Vec, - pub(crate) dependencies: Vec<(DependencyEdgeKind, BoxedSystemSet)>, + pub(crate) dependencies: Vec, pub(crate) ambiguous_with: Ambiguity, } diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index 135ff9341bee6..9229fab210c61 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -457,7 +457,7 @@ impl ScheduleGraph { id: &NodeId, graph_info: &GraphInfo, ) -> Result<(), ScheduleBuildError> { - for (_, set) in &graph_info.dependencies { + for Dependency { kind: _, set } in &graph_info.dependencies { match self.system_set_ids.get(set) { Some(set_id) => { if id == set_id { @@ -508,13 +508,13 @@ impl ScheduleGraph { self.dependency.graph.add_node(id); } - for (edge_kind, set) in dependencies + for (kind, set) in dependencies .into_iter() - .map(|(edge_kind, set)| (edge_kind, self.system_set_ids[&set])) + .map(|Dependency { kind, set }| (kind, self.system_set_ids[&set])) { - let (lhs, rhs) = match edge_kind { - DependencyEdgeKind::Before => (id, set), - DependencyEdgeKind::After => (set, id), + let (lhs, rhs) = match kind { + DependencyKind::Before => (id, set), + DependencyKind::After => (set, id), }; self.dependency.graph.add_edge(lhs, rhs, ()); } From 6a70eef48c26796eddb6501429eca9ce7895820b Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 15 Jan 2023 15:01:38 -0800 Subject: [PATCH 58/64] safety --- .../schedule_v3/executor/multi_threaded.rs | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index 1dcebe4fb4872..8df6525c391c8 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -156,13 +156,16 @@ impl SystemExecutor for MultiThreadedExecutor { // alongside systems that claim the local thread let executor = async { while self.num_completed_systems < num_systems { - self.spawn_system_tasks( - scope, - systems, - &mut conditions, - world, - &mut cached_ready_systems, - ); + // SAFETY: self.ready_systems does not contain running systems + unsafe { + self.spawn_system_tasks( + scope, + systems, + &mut conditions, + world, + &mut cached_ready_systems, + ); + } if self.num_running_systems > 0 { // wait for systems to complete @@ -226,7 +229,10 @@ impl MultiThreadedExecutor { } } - fn spawn_system_tasks<'scope>( + /// # 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], @@ -243,10 +249,12 @@ impl MultiThreadedExecutor { for system_index in ready_systems.ones() { assert!(!self.running_systems.contains(system_index)); - // SAFETY: this system is not running, no other reference exists + // 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 + // 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 @@ -269,7 +277,8 @@ impl MultiThreadedExecutor { self.num_running_systems += 1; if self.system_task_metadata[system_index].is_exclusive { - // SAFETY: `can_run` confirmed no other systems are running + // 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); @@ -277,7 +286,7 @@ impl MultiThreadedExecutor { break; } - // SAFETY: this system is not running, no other reference exists + // SAFETY: No other reference to this system exists. unsafe { self.spawn_system_task(scope, system_index, systems, world); } @@ -385,7 +394,8 @@ impl MultiThreadedExecutor { should_run } - // SAFETY: caller must not alias systems that are running (those are already mutably borrowed) + /// # Safety + /// Caller must not alias systems that are running. unsafe fn spawn_system_task<'scope>( &mut self, scope: &Scope<'_, 'scope, ()>, @@ -430,7 +440,8 @@ impl MultiThreadedExecutor { } } - // SAFETY: caller must ensure that no systems are running + /// # Safety + /// Caller must ensure no systems are currently borrowed. unsafe fn spawn_exclusive_system_task<'scope>( &mut self, scope: &Scope<'_, 'scope, ()>, From 1f86f211d3ec22a40d32353c45e7878142f73b74 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Sun, 15 Jan 2023 16:09:34 -0800 Subject: [PATCH 59/64] clippy, my archenemy --- crates/bevy_ecs/src/schedule_v3/config.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/config.rs b/crates/bevy_ecs/src/schedule_v3/config.rs index 676bf397f99f9..d7a87c374c047 100644 --- a/crates/bevy_ecs/src/schedule_v3/config.rs +++ b/crates/bevy_ecs/src/schedule_v3/config.rs @@ -75,9 +75,7 @@ fn new_condition

(condition: impl Condition

) -> BoxedCondition { fn ambiguous_with(graph_info: &mut GraphInfo, set: BoxedSystemSet) { match &mut graph_info.ambiguous_with { detection @ Ambiguity::Check => { - let mut ambiguous_with = Vec::new(); - ambiguous_with.push(set); - *detection = Ambiguity::IgnoreWithSet(ambiguous_with); + *detection = Ambiguity::IgnoreWithSet(vec![set]); } Ambiguity::IgnoreWithSet(ambiguous_with) => { ambiguous_with.push(set); From cad97b21308a7cbc103ea46affd3579d7f75e002 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Mon, 16 Jan 2023 13:04:29 -0800 Subject: [PATCH 60/64] style --- .../schedule_v3/executor/multi_threaded.rs | 23 ++++++++----------- crates/bevy_ecs/src/schedule_v3/schedule.rs | 6 +---- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index 8df6525c391c8..e05362dd6ef50 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -81,6 +81,8 @@ pub struct MultiThreadedExecutor { evaluated_sets: FixedBitSet, /// Systems that have no remaining dependencies and are waiting to run. ready_systems: FixedBitSet, + /// copy of `ready_systems` + ready_systems_copy: Option, /// Systems that are running. running_systems: FixedBitSet, /// Systems that got skipped. @@ -109,6 +111,7 @@ impl SystemExecutor for MultiThreadedExecutor { self.evaluated_sets = FixedBitSet::with_capacity(set_count); self.ready_systems = FixedBitSet::with_capacity(sys_count); + self.ready_systems_copy = Some(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); @@ -142,9 +145,6 @@ impl SystemExecutor for MultiThreadedExecutor { } } - // using spare bitset to avoid repeated allocations - let mut cached_ready_systems = FixedBitSet::with_capacity(self.ready_systems.len()); - let world = SyncUnsafeCell::from_mut(world); let SyncUnsafeSchedule { systems, @@ -158,13 +158,7 @@ impl SystemExecutor for MultiThreadedExecutor { 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, - &mut cached_ready_systems, - ); + self.spawn_system_tasks(scope, systems, &mut conditions, world); } if self.num_running_systems > 0 { @@ -222,6 +216,7 @@ impl MultiThreadedExecutor { exclusive_running: false, evaluated_sets: FixedBitSet::new(), ready_systems: FixedBitSet::new(), + ready_systems_copy: Some(FixedBitSet::new()), running_systems: FixedBitSet::new(), skipped_systems: FixedBitSet::new(), completed_systems: FixedBitSet::new(), @@ -238,12 +233,13 @@ impl MultiThreadedExecutor { systems: &'scope [SyncUnsafeCell], conditions: &mut Conditions, cell: &'scope SyncUnsafeCell, - ready_systems: &mut FixedBitSet, ) { if self.exclusive_running { return; } + // can't borrow since loop mutably borrows `self` + let mut ready_systems = self.ready_systems_copy.take().unwrap(); ready_systems.clear(); ready_systems.union_with(&self.ready_systems); @@ -264,7 +260,6 @@ impl MultiThreadedExecutor { continue; } - // system is either going to run or be skipped self.ready_systems.set(system_index, false); if !self.should_run(system_index, system, conditions, world) { @@ -272,7 +267,6 @@ impl MultiThreadedExecutor { continue; } - // system is starting self.running_systems.insert(system_index); self.num_running_systems += 1; @@ -291,6 +285,9 @@ impl MultiThreadedExecutor { self.spawn_system_task(scope, system_index, systems, world); } } + + // give back + self.ready_systems_copy = Some(ready_systems); } fn can_run( diff --git a/crates/bevy_ecs/src/schedule_v3/schedule.rs b/crates/bevy_ecs/src/schedule_v3/schedule.rs index 9229fab210c61..2bd7b00eca72f 100644 --- a/crates/bevy_ecs/src/schedule_v3/schedule.rs +++ b/crates/bevy_ecs/src/schedule_v3/schedule.rs @@ -144,7 +144,7 @@ impl Schedule { /// Changes miscellaneous build settings. pub fn set_build_settings(&mut self, settings: ScheduleBuildSettings) -> &mut Self { - self.graph.set_build_settings(settings); + self.graph.settings = settings; self } @@ -307,10 +307,6 @@ impl ScheduleGraph { self.default_set = Some(Box::new(set)); } - fn set_build_settings(&mut self, settings: ScheduleBuildSettings) { - self.settings = settings; - } - fn add_systems

(&mut self, systems: impl IntoSystemConfigs

) { let SystemConfigs { systems, chained } = systems.into_configs(); let mut system_iter = systems.into_iter(); From 1ca012b28b88d2362ae77bc878261be099741a05 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Mon, 16 Jan 2023 13:09:52 -0800 Subject: [PATCH 61/64] fix type signature --- crates/bevy_ecs/src/schedule_v3/set.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/set.rs b/crates/bevy_ecs/src/schedule_v3/set.rs index d62a9ba5267f0..5b29f31e6a3ae 100644 --- a/crates/bevy_ecs/src/schedule_v3/set.rs +++ b/crates/bevy_ecs/src/schedule_v3/set.rs @@ -134,10 +134,11 @@ where } // exclusive systems -impl IntoSystemSet<(IsExclusiveFunctionSystem, Param, Marker)> for F +impl IntoSystemSet<(IsExclusiveFunctionSystem, In, Out, Param, Marker)> + for F where Param: ExclusiveSystemParam, - F: ExclusiveSystemParamFunction, + F: ExclusiveSystemParamFunction, { type Set = SystemTypeSet; From c10c467b6c8a0627aea0f87daa0a0a10b1a906f8 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Mon, 16 Jan 2023 13:20:20 -0800 Subject: [PATCH 62/64] take without option --- .../src/schedule_v3/executor/multi_threaded.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index e05362dd6ef50..c1097a0fad9ce 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -82,7 +82,7 @@ pub struct MultiThreadedExecutor { /// Systems that have no remaining dependencies and are waiting to run. ready_systems: FixedBitSet, /// copy of `ready_systems` - ready_systems_copy: Option, + ready_systems_copy: FixedBitSet, /// Systems that are running. running_systems: FixedBitSet, /// Systems that got skipped. @@ -111,7 +111,7 @@ impl SystemExecutor for MultiThreadedExecutor { self.evaluated_sets = FixedBitSet::with_capacity(set_count); self.ready_systems = FixedBitSet::with_capacity(sys_count); - self.ready_systems_copy = Some(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); @@ -216,7 +216,7 @@ impl MultiThreadedExecutor { exclusive_running: false, evaluated_sets: FixedBitSet::new(), ready_systems: FixedBitSet::new(), - ready_systems_copy: Some(FixedBitSet::new()), + ready_systems_copy: FixedBitSet::new(), running_systems: FixedBitSet::new(), skipped_systems: FixedBitSet::new(), completed_systems: FixedBitSet::new(), @@ -239,7 +239,7 @@ impl MultiThreadedExecutor { } // can't borrow since loop mutably borrows `self` - let mut ready_systems = self.ready_systems_copy.take().unwrap(); + let mut ready_systems = std::mem::take(&mut self.ready_systems_copy); ready_systems.clear(); ready_systems.union_with(&self.ready_systems); @@ -287,7 +287,7 @@ impl MultiThreadedExecutor { } // give back - self.ready_systems_copy = Some(ready_systems); + self.ready_systems_copy = ready_systems; } fn can_run( From c2538854b87b35fee00dcac9264aa4781343e50c Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Mon, 16 Jan 2023 15:39:36 -0800 Subject: [PATCH 63/64] Update crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs --- .../bevy_ecs/src/schedule_v3/executor/multi_threaded.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs index c1097a0fad9ce..3debba3ac59b4 100644 --- a/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs @@ -341,9 +341,11 @@ impl MultiThreadedExecutor { return false; } - // TODO: avoid allocation - self.system_task_metadata[system_index].archetype_component_access = - system.archetype_component_access().clone(); + // 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 From 9a62c72f336fc6d1e5983a445ce3ccfee1aa4f01 Mon Sep 17 00:00:00 2001 From: Cameron <51241057+maniwani@users.noreply.github.com> Date: Mon, 16 Jan 2023 17:38:19 -0800 Subject: [PATCH 64/64] pass the CI vibecheck --- crates/bevy_ecs/src/schedule_v3/mod.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/crates/bevy_ecs/src/schedule_v3/mod.rs b/crates/bevy_ecs/src/schedule_v3/mod.rs index 65b4de80a3140..6a95e28e4a8e0 100644 --- a/crates/bevy_ecs/src/schedule_v3/mod.rs +++ b/crates/bevy_ecs/src/schedule_v3/mod.rs @@ -96,24 +96,21 @@ mod tests { #[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(3)); - - let barrier1 = barrier.clone(); - schedule.add_system(move || { - barrier1.wait(); - }); - let barrier2 = barrier.clone(); - schedule.add_system(move || { - barrier2.wait(); - }); - schedule.add_system(move || { - barrier.wait(); - }); + 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); }