Add bevy_ecs::schedule_v3 module (#6587)
				
					
				
			# Objective Complete the first part of the migration detailed in bevyengine/rfcs#45. ## Solution Add all the new stuff. ### TODO - [x] Impl tuple methods. - [x] Impl chaining. - [x] Port ambiguity detection. - [x] Write docs. - [x] ~~Write more tests.~~(will do later) - [ ] Write changelog and examples here? - [x] ~~Replace `petgraph`.~~ (will do later) Co-authored-by: james7132 <contact@jamessliu.com> Co-authored-by: Michael Hsu <mike.hsu@gmail.com> Co-authored-by: Mike Hsu <mike.hsu@gmail.com>
This commit is contained in:
		
							parent
							
								
									6b4795c428
								
							
						
					
					
						commit
						684f07595f
					
				@ -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_boxed_label, derive_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_boxed_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")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										97
									
								
								crates/bevy_ecs/src/schedule_v3/condition.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								crates/bevy_ecs/src/schedule_v3/condition.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,97 @@
 | 
			
		||||
pub use common_conditions::*;
 | 
			
		||||
 | 
			
		||||
use crate::system::BoxedSystem;
 | 
			
		||||
 | 
			
		||||
pub type BoxedCondition = BoxedSystem<(), bool>;
 | 
			
		||||
 | 
			
		||||
/// A system that determines if one or more scheduled systems should run.
 | 
			
		||||
///
 | 
			
		||||
/// Implemented for functions and closures that convert into [`System<In=(), Out=bool>`](crate::system::System)
 | 
			
		||||
/// with [read-only](crate::system::ReadOnlySystemParam) parameters.
 | 
			
		||||
pub trait Condition<Params>: sealed::Condition<Params> {}
 | 
			
		||||
 | 
			
		||||
impl<Params, F> Condition<Params> for F where F: sealed::Condition<Params> {}
 | 
			
		||||
 | 
			
		||||
mod sealed {
 | 
			
		||||
    use crate::system::{IntoSystem, IsFunctionSystem, ReadOnlySystemParam, SystemParamFunction};
 | 
			
		||||
 | 
			
		||||
    pub trait Condition<Params>: IntoSystem<(), bool, Params> {}
 | 
			
		||||
 | 
			
		||||
    impl<Params, Marker, F> Condition<(IsFunctionSystem, Params, Marker)> for F
 | 
			
		||||
    where
 | 
			
		||||
        F: SystemParamFunction<(), bool, Params, Marker> + Send + Sync + 'static,
 | 
			
		||||
        Params: ReadOnlySystemParam + 'static,
 | 
			
		||||
        Marker: 'static,
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
mod common_conditions {
 | 
			
		||||
    use crate::schedule_v3::{State, States};
 | 
			
		||||
    use crate::system::{Res, Resource};
 | 
			
		||||
 | 
			
		||||
    /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true`
 | 
			
		||||
    /// if the resource exists.
 | 
			
		||||
    pub fn resource_exists<T>() -> impl FnMut(Option<Res<T>>) -> bool
 | 
			
		||||
    where
 | 
			
		||||
        T: Resource,
 | 
			
		||||
    {
 | 
			
		||||
        move |res: Option<Res<T>>| res.is_some()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true`
 | 
			
		||||
    /// if the resource is equal to `value`.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Panics
 | 
			
		||||
    ///
 | 
			
		||||
    /// The condition will panic if the resource does not exist.
 | 
			
		||||
    pub fn resource_equals<T>(value: T) -> impl FnMut(Res<T>) -> bool
 | 
			
		||||
    where
 | 
			
		||||
        T: Resource + PartialEq,
 | 
			
		||||
    {
 | 
			
		||||
        move |res: Res<T>| *res == value
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true`
 | 
			
		||||
    /// if the resource exists and is equal to `value`.
 | 
			
		||||
    ///
 | 
			
		||||
    /// The condition will return `false` if the resource does not exist.
 | 
			
		||||
    pub fn resource_exists_and_equals<T>(value: T) -> impl FnMut(Option<Res<T>>) -> bool
 | 
			
		||||
    where
 | 
			
		||||
        T: Resource + PartialEq,
 | 
			
		||||
    {
 | 
			
		||||
        move |res: Option<Res<T>>| match res {
 | 
			
		||||
            Some(res) => *res == value,
 | 
			
		||||
            None => false,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true`
 | 
			
		||||
    /// if the state machine exists.
 | 
			
		||||
    pub fn state_exists<S: States>() -> impl FnMut(Option<Res<State<S>>>) -> bool {
 | 
			
		||||
        move |current_state: Option<Res<State<S>>>| current_state.is_some()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true`
 | 
			
		||||
    /// if the state machine is currently in `state`.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Panics
 | 
			
		||||
    ///
 | 
			
		||||
    /// The condition will panic if the resource does not exist.
 | 
			
		||||
    pub fn state_equals<S: States>(state: S) -> impl FnMut(Res<State<S>>) -> bool {
 | 
			
		||||
        move |current_state: Res<State<S>>| current_state.0 == state
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true`
 | 
			
		||||
    /// if the state machine exists and is currently in `state`.
 | 
			
		||||
    ///
 | 
			
		||||
    /// The condition will return `false` if the state does not exist.
 | 
			
		||||
    pub fn state_exists_and_equals<S: States>(
 | 
			
		||||
        state: S,
 | 
			
		||||
    ) -> impl FnMut(Option<Res<State<S>>>) -> bool {
 | 
			
		||||
        move |current_state: Option<Res<State<S>>>| match current_state {
 | 
			
		||||
            Some(current_state) => current_state.0 == state,
 | 
			
		||||
            None => false,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										649
									
								
								crates/bevy_ecs/src/schedule_v3/config.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										649
									
								
								crates/bevy_ecs/src/schedule_v3/config.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,649 @@
 | 
			
		||||
use bevy_ecs_macros::all_tuples;
 | 
			
		||||
use bevy_utils::default;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    schedule_v3::{
 | 
			
		||||
        condition::{BoxedCondition, Condition},
 | 
			
		||||
        graph_utils::{Ambiguity, Dependency, DependencyKind, GraphInfo},
 | 
			
		||||
        set::{BoxedSystemSet, IntoSystemSet, SystemSet},
 | 
			
		||||
    },
 | 
			
		||||
    system::{BoxedSystem, IntoSystem, System},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// A [`SystemSet`] with scheduling metadata.
 | 
			
		||||
pub struct SystemSetConfig {
 | 
			
		||||
    pub(super) set: BoxedSystemSet,
 | 
			
		||||
    pub(super) graph_info: GraphInfo,
 | 
			
		||||
    pub(super) conditions: Vec<BoxedCondition>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SystemSetConfig {
 | 
			
		||||
    fn new(set: BoxedSystemSet) -> Self {
 | 
			
		||||
        // system type sets are automatically populated
 | 
			
		||||
        // to avoid unintentionally broad changes, they cannot be configured
 | 
			
		||||
        assert!(
 | 
			
		||||
            !set.is_system_type(),
 | 
			
		||||
            "configuring system type sets is not allowed"
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            set,
 | 
			
		||||
            graph_info: GraphInfo {
 | 
			
		||||
                sets: Vec::new(),
 | 
			
		||||
                dependencies: Vec::new(),
 | 
			
		||||
                ambiguous_with: default(),
 | 
			
		||||
            },
 | 
			
		||||
            conditions: Vec::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A [`System`] with scheduling metadata.
 | 
			
		||||
pub struct SystemConfig {
 | 
			
		||||
    pub(super) system: BoxedSystem,
 | 
			
		||||
    pub(super) graph_info: GraphInfo,
 | 
			
		||||
    pub(super) conditions: Vec<BoxedCondition>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SystemConfig {
 | 
			
		||||
    fn new(system: BoxedSystem) -> Self {
 | 
			
		||||
        // include system in its default sets
 | 
			
		||||
        let sets = system.default_system_sets().into_iter().collect();
 | 
			
		||||
        Self {
 | 
			
		||||
            system,
 | 
			
		||||
            graph_info: GraphInfo {
 | 
			
		||||
                sets,
 | 
			
		||||
                dependencies: Vec::new(),
 | 
			
		||||
                ambiguous_with: default(),
 | 
			
		||||
            },
 | 
			
		||||
            conditions: Vec::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn new_condition<P>(condition: impl Condition<P>) -> BoxedCondition {
 | 
			
		||||
    let condition_system = IntoSystem::into_system(condition);
 | 
			
		||||
    assert!(
 | 
			
		||||
        condition_system.is_send(),
 | 
			
		||||
        "Condition `{}` accesses thread-local resources. This is not currently supported.",
 | 
			
		||||
        condition_system.name()
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    Box::new(condition_system)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn ambiguous_with(graph_info: &mut GraphInfo, set: BoxedSystemSet) {
 | 
			
		||||
    match &mut graph_info.ambiguous_with {
 | 
			
		||||
        detection @ Ambiguity::Check => {
 | 
			
		||||
            *detection = Ambiguity::IgnoreWithSet(vec![set]);
 | 
			
		||||
        }
 | 
			
		||||
        Ambiguity::IgnoreWithSet(ambiguous_with) => {
 | 
			
		||||
            ambiguous_with.push(set);
 | 
			
		||||
        }
 | 
			
		||||
        Ambiguity::IgnoreAll => (),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Types that can be converted into a [`SystemSetConfig`].
 | 
			
		||||
///
 | 
			
		||||
/// This has been implemented for all types that implement [`SystemSet`] and boxed trait objects.
 | 
			
		||||
pub trait IntoSystemSetConfig: sealed::IntoSystemSetConfig {
 | 
			
		||||
    /// Convert into a [`SystemSetConfig`].
 | 
			
		||||
    #[doc(hidden)]
 | 
			
		||||
    fn into_config(self) -> SystemSetConfig;
 | 
			
		||||
    /// Add to the provided `set`.
 | 
			
		||||
    fn in_set(self, set: impl SystemSet) -> SystemSetConfig;
 | 
			
		||||
    /// Run before all systems in `set`.
 | 
			
		||||
    fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig;
 | 
			
		||||
    /// Run after all systems in `set`.
 | 
			
		||||
    fn after<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig;
 | 
			
		||||
    /// Run the systems in this set only if the [`Condition`] is `true`.
 | 
			
		||||
    ///
 | 
			
		||||
    /// The `Condition` will be evaluated at most once (per schedule run),
 | 
			
		||||
    /// the first time a system in this set prepares to run.
 | 
			
		||||
    fn run_if<P>(self, condition: impl Condition<P>) -> SystemSetConfig;
 | 
			
		||||
    /// Suppress warnings and errors that would result from systems in this set having ambiguities
 | 
			
		||||
    /// (conflicting access but indeterminate order) with systems in `set`.
 | 
			
		||||
    fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig;
 | 
			
		||||
    /// Suppress warnings and errors that would result from systems in this set having ambiguities
 | 
			
		||||
    /// (conflicting access but indeterminate order) with any other system.
 | 
			
		||||
    fn ambiguous_with_all(self) -> SystemSetConfig;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<S> IntoSystemSetConfig for S
 | 
			
		||||
where
 | 
			
		||||
    S: SystemSet + sealed::IntoSystemSetConfig,
 | 
			
		||||
{
 | 
			
		||||
    fn into_config(self) -> SystemSetConfig {
 | 
			
		||||
        SystemSetConfig::new(Box::new(self))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn in_set(self, set: impl SystemSet) -> SystemSetConfig {
 | 
			
		||||
        SystemSetConfig::new(Box::new(self)).in_set(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig {
 | 
			
		||||
        SystemSetConfig::new(Box::new(self)).before(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn after<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig {
 | 
			
		||||
        SystemSetConfig::new(Box::new(self)).after(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn run_if<P>(self, condition: impl Condition<P>) -> SystemSetConfig {
 | 
			
		||||
        SystemSetConfig::new(Box::new(self)).run_if(condition)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig {
 | 
			
		||||
        SystemSetConfig::new(Box::new(self)).ambiguous_with(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ambiguous_with_all(self) -> SystemSetConfig {
 | 
			
		||||
        SystemSetConfig::new(Box::new(self)).ambiguous_with_all()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IntoSystemSetConfig for BoxedSystemSet {
 | 
			
		||||
    fn into_config(self) -> SystemSetConfig {
 | 
			
		||||
        SystemSetConfig::new(self)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn in_set(self, set: impl SystemSet) -> SystemSetConfig {
 | 
			
		||||
        SystemSetConfig::new(self).in_set(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig {
 | 
			
		||||
        SystemSetConfig::new(self).before(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn after<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig {
 | 
			
		||||
        SystemSetConfig::new(self).after(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn run_if<P>(self, condition: impl Condition<P>) -> SystemSetConfig {
 | 
			
		||||
        SystemSetConfig::new(self).run_if(condition)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig {
 | 
			
		||||
        SystemSetConfig::new(self).ambiguous_with(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ambiguous_with_all(self) -> SystemSetConfig {
 | 
			
		||||
        SystemSetConfig::new(self).ambiguous_with_all()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IntoSystemSetConfig for SystemSetConfig {
 | 
			
		||||
    fn into_config(self) -> Self {
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn in_set(mut self, set: impl SystemSet) -> Self {
 | 
			
		||||
        assert!(
 | 
			
		||||
            !set.is_system_type(),
 | 
			
		||||
            "adding arbitrary systems to a system type set is not allowed"
 | 
			
		||||
        );
 | 
			
		||||
        self.graph_info.sets.push(Box::new(set));
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn before<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
 | 
			
		||||
        self.graph_info.dependencies.push(Dependency::new(
 | 
			
		||||
            DependencyKind::Before,
 | 
			
		||||
            Box::new(set.into_system_set()),
 | 
			
		||||
        ));
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn after<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
 | 
			
		||||
        self.graph_info.dependencies.push(Dependency::new(
 | 
			
		||||
            DependencyKind::After,
 | 
			
		||||
            Box::new(set.into_system_set()),
 | 
			
		||||
        ));
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn run_if<P>(mut self, condition: impl Condition<P>) -> Self {
 | 
			
		||||
        self.conditions.push(new_condition(condition));
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ambiguous_with<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
 | 
			
		||||
        ambiguous_with(&mut self.graph_info, Box::new(set.into_system_set()));
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ambiguous_with_all(mut self) -> Self {
 | 
			
		||||
        self.graph_info.ambiguous_with = Ambiguity::IgnoreAll;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Types that can be converted into a [`SystemConfig`].
 | 
			
		||||
///
 | 
			
		||||
/// This has been implemented for boxed [`System<In=(), Out=()>`](crate::system::System)
 | 
			
		||||
/// trait objects and all functions that turn into such.
 | 
			
		||||
pub trait IntoSystemConfig<Params>: sealed::IntoSystemConfig<Params> {
 | 
			
		||||
    /// Convert into a [`SystemConfig`].
 | 
			
		||||
    #[doc(hidden)]
 | 
			
		||||
    fn into_config(self) -> SystemConfig;
 | 
			
		||||
    /// Add to `set` membership.
 | 
			
		||||
    fn in_set(self, set: impl SystemSet) -> SystemConfig;
 | 
			
		||||
    /// Run before all systems in `set`.
 | 
			
		||||
    fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemConfig;
 | 
			
		||||
    /// Run after all systems in `set`.
 | 
			
		||||
    fn after<M>(self, set: impl IntoSystemSet<M>) -> SystemConfig;
 | 
			
		||||
    /// Run only if the [`Condition`] is `true`.
 | 
			
		||||
    ///
 | 
			
		||||
    /// The `Condition` will be evaluated at most once (per schedule run),
 | 
			
		||||
    /// when the system prepares to run.
 | 
			
		||||
    fn run_if<P>(self, condition: impl Condition<P>) -> SystemConfig;
 | 
			
		||||
    /// Suppress warnings and errors that would result from this system having ambiguities
 | 
			
		||||
    /// (conflicting access but indeterminate order) with systems in `set`.
 | 
			
		||||
    fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> SystemConfig;
 | 
			
		||||
    /// Suppress warnings and errors that would result from this system having ambiguities
 | 
			
		||||
    /// (conflicting access but indeterminate order) with any other system.
 | 
			
		||||
    fn ambiguous_with_all(self) -> SystemConfig;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<Params, F> IntoSystemConfig<Params> for F
 | 
			
		||||
where
 | 
			
		||||
    F: IntoSystem<(), (), Params> + sealed::IntoSystemConfig<Params>,
 | 
			
		||||
{
 | 
			
		||||
    fn into_config(self) -> SystemConfig {
 | 
			
		||||
        SystemConfig::new(Box::new(IntoSystem::into_system(self)))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn in_set(self, set: impl SystemSet) -> SystemConfig {
 | 
			
		||||
        SystemConfig::new(Box::new(IntoSystem::into_system(self))).in_set(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemConfig {
 | 
			
		||||
        SystemConfig::new(Box::new(IntoSystem::into_system(self))).before(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn after<M>(self, set: impl IntoSystemSet<M>) -> SystemConfig {
 | 
			
		||||
        SystemConfig::new(Box::new(IntoSystem::into_system(self))).after(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn run_if<P>(self, condition: impl Condition<P>) -> SystemConfig {
 | 
			
		||||
        SystemConfig::new(Box::new(IntoSystem::into_system(self))).run_if(condition)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> SystemConfig {
 | 
			
		||||
        SystemConfig::new(Box::new(IntoSystem::into_system(self))).ambiguous_with(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ambiguous_with_all(self) -> SystemConfig {
 | 
			
		||||
        SystemConfig::new(Box::new(IntoSystem::into_system(self))).ambiguous_with_all()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IntoSystemConfig<()> for BoxedSystem<(), ()> {
 | 
			
		||||
    fn into_config(self) -> SystemConfig {
 | 
			
		||||
        SystemConfig::new(self)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn in_set(self, set: impl SystemSet) -> SystemConfig {
 | 
			
		||||
        SystemConfig::new(self).in_set(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemConfig {
 | 
			
		||||
        SystemConfig::new(self).before(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn after<M>(self, set: impl IntoSystemSet<M>) -> SystemConfig {
 | 
			
		||||
        SystemConfig::new(self).after(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn run_if<P>(self, condition: impl Condition<P>) -> SystemConfig {
 | 
			
		||||
        SystemConfig::new(self).run_if(condition)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> SystemConfig {
 | 
			
		||||
        SystemConfig::new(self).ambiguous_with(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ambiguous_with_all(self) -> SystemConfig {
 | 
			
		||||
        SystemConfig::new(self).ambiguous_with_all()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IntoSystemConfig<()> for SystemConfig {
 | 
			
		||||
    fn into_config(self) -> Self {
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn in_set(mut self, set: impl SystemSet) -> Self {
 | 
			
		||||
        assert!(
 | 
			
		||||
            !set.is_system_type(),
 | 
			
		||||
            "adding arbitrary systems to a system type set is not allowed"
 | 
			
		||||
        );
 | 
			
		||||
        self.graph_info.sets.push(Box::new(set));
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn before<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
 | 
			
		||||
        self.graph_info.dependencies.push(Dependency::new(
 | 
			
		||||
            DependencyKind::Before,
 | 
			
		||||
            Box::new(set.into_system_set()),
 | 
			
		||||
        ));
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn after<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
 | 
			
		||||
        self.graph_info.dependencies.push(Dependency::new(
 | 
			
		||||
            DependencyKind::After,
 | 
			
		||||
            Box::new(set.into_system_set()),
 | 
			
		||||
        ));
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn run_if<P>(mut self, condition: impl Condition<P>) -> Self {
 | 
			
		||||
        self.conditions.push(new_condition(condition));
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ambiguous_with<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
 | 
			
		||||
        ambiguous_with(&mut self.graph_info, Box::new(set.into_system_set()));
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ambiguous_with_all(mut self) -> Self {
 | 
			
		||||
        self.graph_info.ambiguous_with = Ambiguity::IgnoreAll;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// only `System<In=(), Out=()>` system objects can be scheduled
 | 
			
		||||
mod sealed {
 | 
			
		||||
    use crate::{
 | 
			
		||||
        schedule_v3::{BoxedSystemSet, SystemSet},
 | 
			
		||||
        system::{BoxedSystem, IntoSystem},
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    use super::{SystemConfig, SystemSetConfig};
 | 
			
		||||
 | 
			
		||||
    pub trait IntoSystemConfig<Params> {}
 | 
			
		||||
 | 
			
		||||
    impl<Params, F: IntoSystem<(), (), Params>> IntoSystemConfig<Params> for F {}
 | 
			
		||||
 | 
			
		||||
    impl IntoSystemConfig<()> for BoxedSystem<(), ()> {}
 | 
			
		||||
 | 
			
		||||
    impl IntoSystemConfig<()> for SystemConfig {}
 | 
			
		||||
 | 
			
		||||
    pub trait IntoSystemSetConfig {}
 | 
			
		||||
 | 
			
		||||
    impl<S: SystemSet> IntoSystemSetConfig for S {}
 | 
			
		||||
 | 
			
		||||
    impl IntoSystemSetConfig for BoxedSystemSet {}
 | 
			
		||||
 | 
			
		||||
    impl IntoSystemSetConfig for SystemSetConfig {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A collection of [`SystemConfig`].
 | 
			
		||||
pub struct SystemConfigs {
 | 
			
		||||
    pub(super) systems: Vec<SystemConfig>,
 | 
			
		||||
    /// If `true`, adds `before -> after` ordering constraints between the successive elements.
 | 
			
		||||
    pub(super) chained: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Types that can convert into a [`SystemConfigs`].
 | 
			
		||||
pub trait IntoSystemConfigs<Params>
 | 
			
		||||
where
 | 
			
		||||
    Self: Sized,
 | 
			
		||||
{
 | 
			
		||||
    /// Convert into a [`SystemConfigs`].
 | 
			
		||||
    #[doc(hidden)]
 | 
			
		||||
    fn into_configs(self) -> SystemConfigs;
 | 
			
		||||
 | 
			
		||||
    /// Add these systems to the provided `set`.
 | 
			
		||||
    fn in_set(self, set: impl SystemSet) -> SystemConfigs {
 | 
			
		||||
        self.into_configs().in_set(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Run before all systems in `set`.
 | 
			
		||||
    fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemConfigs {
 | 
			
		||||
        self.into_configs().before(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Run after all systems in `set`.
 | 
			
		||||
    fn after<M>(self, set: impl IntoSystemSet<M>) -> SystemConfigs {
 | 
			
		||||
        self.into_configs().after(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Suppress warnings and errors that would result from these systems having ambiguities
 | 
			
		||||
    /// (conflicting access but indeterminate order) with systems in `set`.
 | 
			
		||||
    fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> SystemConfigs {
 | 
			
		||||
        self.into_configs().ambiguous_with(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Suppress warnings and errors that would result from these systems having ambiguities
 | 
			
		||||
    /// (conflicting access but indeterminate order) with any other system.
 | 
			
		||||
    fn ambiguous_with_all(self) -> SystemConfigs {
 | 
			
		||||
        self.into_configs().ambiguous_with_all()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Treat this collection as a sequence of systems.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Ordering constraints will be applied between the successive elements.
 | 
			
		||||
    fn chain(self) -> SystemConfigs {
 | 
			
		||||
        self.into_configs().chain()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IntoSystemConfigs<()> for SystemConfigs {
 | 
			
		||||
    fn into_configs(self) -> Self {
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn in_set(mut self, set: impl SystemSet) -> Self {
 | 
			
		||||
        assert!(
 | 
			
		||||
            !set.is_system_type(),
 | 
			
		||||
            "adding arbitrary systems to a system type set is not allowed"
 | 
			
		||||
        );
 | 
			
		||||
        for config in &mut self.systems {
 | 
			
		||||
            config.graph_info.sets.push(set.dyn_clone());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn before<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
 | 
			
		||||
        let set = set.into_system_set();
 | 
			
		||||
        for config in &mut self.systems {
 | 
			
		||||
            config
 | 
			
		||||
                .graph_info
 | 
			
		||||
                .dependencies
 | 
			
		||||
                .push(Dependency::new(DependencyKind::Before, set.dyn_clone()));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn after<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
 | 
			
		||||
        let set = set.into_system_set();
 | 
			
		||||
        for config in &mut self.systems {
 | 
			
		||||
            config
 | 
			
		||||
                .graph_info
 | 
			
		||||
                .dependencies
 | 
			
		||||
                .push(Dependency::new(DependencyKind::After, set.dyn_clone()));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ambiguous_with<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
 | 
			
		||||
        let set = set.into_system_set();
 | 
			
		||||
        for config in &mut self.systems {
 | 
			
		||||
            ambiguous_with(&mut config.graph_info, set.dyn_clone());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ambiguous_with_all(mut self) -> Self {
 | 
			
		||||
        for config in &mut self.systems {
 | 
			
		||||
            config.graph_info.ambiguous_with = Ambiguity::IgnoreAll;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn chain(mut self) -> Self {
 | 
			
		||||
        self.chained = true;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A collection of [`SystemSetConfig`].
 | 
			
		||||
pub struct SystemSetConfigs {
 | 
			
		||||
    pub(super) sets: Vec<SystemSetConfig>,
 | 
			
		||||
    /// If `true`, adds `before -> after` ordering constraints between the successive elements.
 | 
			
		||||
    pub(super) chained: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Types that can convert into a [`SystemSetConfigs`].
 | 
			
		||||
pub trait IntoSystemSetConfigs
 | 
			
		||||
where
 | 
			
		||||
    Self: Sized,
 | 
			
		||||
{
 | 
			
		||||
    /// Convert into a [`SystemSetConfigs`].
 | 
			
		||||
    #[doc(hidden)]
 | 
			
		||||
    fn into_configs(self) -> SystemSetConfigs;
 | 
			
		||||
 | 
			
		||||
    /// Add these system sets to the provided `set`.
 | 
			
		||||
    fn in_set(self, set: impl SystemSet) -> SystemSetConfigs {
 | 
			
		||||
        self.into_configs().in_set(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Run before all systems in `set`.
 | 
			
		||||
    fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfigs {
 | 
			
		||||
        self.into_configs().before(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Run after all systems in `set`.
 | 
			
		||||
    fn after<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfigs {
 | 
			
		||||
        self.into_configs().after(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Suppress warnings and errors that would result from systems in these sets having ambiguities
 | 
			
		||||
    /// (conflicting access but indeterminate order) with systems in `set`.
 | 
			
		||||
    fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfigs {
 | 
			
		||||
        self.into_configs().ambiguous_with(set)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Suppress warnings and errors that would result from systems in these sets having ambiguities
 | 
			
		||||
    /// (conflicting access but indeterminate order) with any other system.
 | 
			
		||||
    fn ambiguous_with_all(self) -> SystemSetConfigs {
 | 
			
		||||
        self.into_configs().ambiguous_with_all()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Treat this collection as a sequence of system sets.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Ordering constraints will be applied between the successive elements.
 | 
			
		||||
    fn chain(self) -> SystemSetConfigs {
 | 
			
		||||
        self.into_configs().chain()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IntoSystemSetConfigs for SystemSetConfigs {
 | 
			
		||||
    fn into_configs(self) -> Self {
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn in_set(mut self, set: impl SystemSet) -> Self {
 | 
			
		||||
        assert!(
 | 
			
		||||
            !set.is_system_type(),
 | 
			
		||||
            "adding arbitrary systems to a system type set is not allowed"
 | 
			
		||||
        );
 | 
			
		||||
        for config in &mut self.sets {
 | 
			
		||||
            config.graph_info.sets.push(set.dyn_clone());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn before<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
 | 
			
		||||
        let set = set.into_system_set();
 | 
			
		||||
        for config in &mut self.sets {
 | 
			
		||||
            config
 | 
			
		||||
                .graph_info
 | 
			
		||||
                .dependencies
 | 
			
		||||
                .push(Dependency::new(DependencyKind::Before, set.dyn_clone()));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn after<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
 | 
			
		||||
        let set = set.into_system_set();
 | 
			
		||||
        for config in &mut self.sets {
 | 
			
		||||
            config
 | 
			
		||||
                .graph_info
 | 
			
		||||
                .dependencies
 | 
			
		||||
                .push(Dependency::new(DependencyKind::After, set.dyn_clone()));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ambiguous_with<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
 | 
			
		||||
        let set = set.into_system_set();
 | 
			
		||||
        for config in &mut self.sets {
 | 
			
		||||
            ambiguous_with(&mut config.graph_info, set.dyn_clone());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ambiguous_with_all(mut self) -> Self {
 | 
			
		||||
        for config in &mut self.sets {
 | 
			
		||||
            config.graph_info.ambiguous_with = Ambiguity::IgnoreAll;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn chain(mut self) -> Self {
 | 
			
		||||
        self.chained = true;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
macro_rules! impl_system_collection {
 | 
			
		||||
    ($(($param: ident, $sys: ident)),*) => {
 | 
			
		||||
        impl<$($param, $sys),*> IntoSystemConfigs<($($param,)*)> for ($($sys,)*)
 | 
			
		||||
        where
 | 
			
		||||
            $($sys: IntoSystemConfig<$param>),*
 | 
			
		||||
        {
 | 
			
		||||
            #[allow(non_snake_case)]
 | 
			
		||||
            fn into_configs(self) -> SystemConfigs {
 | 
			
		||||
                let ($($sys,)*) = self;
 | 
			
		||||
                SystemConfigs {
 | 
			
		||||
                    systems: vec![$($sys.into_config(),)*],
 | 
			
		||||
                    chained: false,
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
macro_rules! impl_system_set_collection {
 | 
			
		||||
    ($($set: ident),*) => {
 | 
			
		||||
        impl<$($set: IntoSystemSetConfig),*> IntoSystemSetConfigs for ($($set,)*)
 | 
			
		||||
        {
 | 
			
		||||
            #[allow(non_snake_case)]
 | 
			
		||||
            fn into_configs(self) -> SystemSetConfigs {
 | 
			
		||||
                let ($($set,)*) = self;
 | 
			
		||||
                SystemSetConfigs {
 | 
			
		||||
                    sets: vec![$($set.into_config(),)*],
 | 
			
		||||
                    chained: false,
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
all_tuples!(impl_system_collection, 0, 15, P, S);
 | 
			
		||||
all_tuples!(impl_system_set_collection, 0, 15, S);
 | 
			
		||||
							
								
								
									
										90
									
								
								crates/bevy_ecs/src/schedule_v3/executor/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								crates/bevy_ecs/src/schedule_v3/executor/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,90 @@
 | 
			
		||||
mod multi_threaded;
 | 
			
		||||
mod simple;
 | 
			
		||||
mod single_threaded;
 | 
			
		||||
 | 
			
		||||
pub use self::multi_threaded::MultiThreadedExecutor;
 | 
			
		||||
pub use self::simple::SimpleExecutor;
 | 
			
		||||
pub use self::single_threaded::SingleThreadedExecutor;
 | 
			
		||||
 | 
			
		||||
use fixedbitset::FixedBitSet;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    schedule_v3::{BoxedCondition, NodeId},
 | 
			
		||||
    system::BoxedSystem,
 | 
			
		||||
    world::World,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Types that can run a [`SystemSchedule`] on a [`World`].
 | 
			
		||||
pub(super) trait SystemExecutor: Send + Sync {
 | 
			
		||||
    fn kind(&self) -> ExecutorKind;
 | 
			
		||||
    fn init(&mut self, schedule: &SystemSchedule);
 | 
			
		||||
    fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Specifies how a [`Schedule`](super::Schedule) will be run.
 | 
			
		||||
///
 | 
			
		||||
/// [`MultiThreaded`](ExecutorKind::MultiThreaded) is the default.
 | 
			
		||||
#[derive(PartialEq, Eq, Default)]
 | 
			
		||||
pub enum ExecutorKind {
 | 
			
		||||
    /// Runs the schedule using a single thread.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Useful if you're dealing with a single-threaded environment, saving your threads for
 | 
			
		||||
    /// other things, or just trying minimize overhead.
 | 
			
		||||
    SingleThreaded,
 | 
			
		||||
    /// Like [`SingleThreaded`](ExecutorKind::SingleThreaded) but calls [`apply_buffers`](crate::system::System::apply_buffers)
 | 
			
		||||
    /// immediately after running each system.
 | 
			
		||||
    Simple,
 | 
			
		||||
    /// Runs the schedule using a thread pool. Non-conflicting systems can run in parallel.
 | 
			
		||||
    #[default]
 | 
			
		||||
    MultiThreaded,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Holds systems and conditions of a [`Schedule`](super::Schedule) sorted in topological order
 | 
			
		||||
/// (along with dependency information for multi-threaded execution).
 | 
			
		||||
///
 | 
			
		||||
/// Since the arrays are sorted in the same order, elements are referenced by their index.
 | 
			
		||||
/// `FixedBitSet` is used as a smaller, more efficient substitute of `HashSet<usize>`.
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
pub(super) struct SystemSchedule {
 | 
			
		||||
    pub(super) systems: Vec<BoxedSystem>,
 | 
			
		||||
    pub(super) system_conditions: Vec<Vec<BoxedCondition>>,
 | 
			
		||||
    pub(super) set_conditions: Vec<Vec<BoxedCondition>>,
 | 
			
		||||
    pub(super) system_ids: Vec<NodeId>,
 | 
			
		||||
    pub(super) set_ids: Vec<NodeId>,
 | 
			
		||||
    pub(super) system_dependencies: Vec<usize>,
 | 
			
		||||
    pub(super) system_dependents: Vec<Vec<usize>>,
 | 
			
		||||
    pub(super) sets_of_systems: Vec<FixedBitSet>,
 | 
			
		||||
    pub(super) systems_in_sets: Vec<FixedBitSet>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SystemSchedule {
 | 
			
		||||
    pub const fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            systems: Vec::new(),
 | 
			
		||||
            system_conditions: Vec::new(),
 | 
			
		||||
            set_conditions: Vec::new(),
 | 
			
		||||
            system_ids: Vec::new(),
 | 
			
		||||
            set_ids: Vec::new(),
 | 
			
		||||
            system_dependencies: Vec::new(),
 | 
			
		||||
            system_dependents: Vec::new(),
 | 
			
		||||
            sets_of_systems: Vec::new(),
 | 
			
		||||
            systems_in_sets: Vec::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Instructs the executor to call [`apply_buffers`](crate::system::System::apply_buffers)
 | 
			
		||||
/// on the systems that have run but not applied their buffers.
 | 
			
		||||
///
 | 
			
		||||
/// **Notes**
 | 
			
		||||
/// - This function (currently) does nothing if it's called manually or wrapped inside a [`PipeSystem`](crate::system::PipeSystem).
 | 
			
		||||
/// - Modifying a [`Schedule`](super::Schedule) may change the order buffers are applied.
 | 
			
		||||
#[allow(unused_variables)]
 | 
			
		||||
pub fn apply_system_buffers(world: &mut World) {}
 | 
			
		||||
 | 
			
		||||
/// Returns `true` if the [`System`](crate::system::System) is an instance of [`apply_system_buffers`].
 | 
			
		||||
pub(super) fn is_apply_system_buffers(system: &BoxedSystem) -> bool {
 | 
			
		||||
    use std::any::Any;
 | 
			
		||||
    // deref to use `System::type_id` instead of `Any::type_id`
 | 
			
		||||
    system.as_ref().type_id() == apply_system_buffers.type_id()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										575
									
								
								crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										575
									
								
								crates/bevy_ecs/src/schedule_v3/executor/multi_threaded.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,575 @@
 | 
			
		||||
use bevy_tasks::{ComputeTaskPool, Scope, TaskPool};
 | 
			
		||||
use bevy_utils::default;
 | 
			
		||||
use bevy_utils::syncunsafecell::SyncUnsafeCell;
 | 
			
		||||
#[cfg(feature = "trace")]
 | 
			
		||||
use bevy_utils::tracing::{info_span, Instrument};
 | 
			
		||||
 | 
			
		||||
use async_channel::{Receiver, Sender};
 | 
			
		||||
use fixedbitset::FixedBitSet;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    archetype::ArchetypeComponentId,
 | 
			
		||||
    query::Access,
 | 
			
		||||
    schedule_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<BoxedSystem>],
 | 
			
		||||
    conditions: Conditions<'a>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Conditions<'a> {
 | 
			
		||||
    system_conditions: &'a mut [Vec<BoxedCondition>],
 | 
			
		||||
    set_conditions: &'a mut [Vec<BoxedCondition>],
 | 
			
		||||
    sets_of_systems: &'a [FixedBitSet],
 | 
			
		||||
    systems_in_sets: &'a [FixedBitSet],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SyncUnsafeSchedule<'_> {
 | 
			
		||||
    fn new(schedule: &mut SystemSchedule) -> SyncUnsafeSchedule<'_> {
 | 
			
		||||
        SyncUnsafeSchedule {
 | 
			
		||||
            systems: SyncUnsafeCell::from_mut(schedule.systems.as_mut_slice()).as_slice_of_cells(),
 | 
			
		||||
            conditions: Conditions {
 | 
			
		||||
                system_conditions: &mut schedule.system_conditions,
 | 
			
		||||
                set_conditions: &mut schedule.set_conditions,
 | 
			
		||||
                sets_of_systems: &schedule.sets_of_systems,
 | 
			
		||||
                systems_in_sets: &schedule.systems_in_sets,
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Per-system data used by the [`MultiThreadedExecutor`].
 | 
			
		||||
// Copied here because it can't be read from the system when it's running.
 | 
			
		||||
struct SystemTaskMetadata {
 | 
			
		||||
    /// The `ArchetypeComponentId` access of the system.
 | 
			
		||||
    archetype_component_access: Access<ArchetypeComponentId>,
 | 
			
		||||
    /// Indices of the systems that directly depend on the system.
 | 
			
		||||
    dependents: Vec<usize>,
 | 
			
		||||
    /// Is `true` if the system does not access `!Send` data.
 | 
			
		||||
    is_send: bool,
 | 
			
		||||
    /// Is `true` if the system is exclusive.
 | 
			
		||||
    is_exclusive: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Runs the schedule using a thread pool. Non-conflicting systems can run in parallel.
 | 
			
		||||
pub struct MultiThreadedExecutor {
 | 
			
		||||
    /// Sends system completion events.
 | 
			
		||||
    sender: Sender<usize>,
 | 
			
		||||
    /// Receives system completion events.
 | 
			
		||||
    receiver: Receiver<usize>,
 | 
			
		||||
    /// Metadata for scheduling and running system tasks.
 | 
			
		||||
    system_task_metadata: Vec<SystemTaskMetadata>,
 | 
			
		||||
    /// Union of the accesses of all currently running systems.
 | 
			
		||||
    active_access: Access<ArchetypeComponentId>,
 | 
			
		||||
    /// 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<usize>,
 | 
			
		||||
    /// System sets whose conditions have been evaluated.
 | 
			
		||||
    evaluated_sets: FixedBitSet,
 | 
			
		||||
    /// Systems that have no remaining dependencies and are waiting to run.
 | 
			
		||||
    ready_systems: FixedBitSet,
 | 
			
		||||
    /// copy of `ready_systems`
 | 
			
		||||
    ready_systems_copy: FixedBitSet,
 | 
			
		||||
    /// Systems that are running.
 | 
			
		||||
    running_systems: FixedBitSet,
 | 
			
		||||
    /// Systems that got skipped.
 | 
			
		||||
    skipped_systems: FixedBitSet,
 | 
			
		||||
    /// Systems whose conditions have been evaluated and were run or skipped.
 | 
			
		||||
    completed_systems: FixedBitSet,
 | 
			
		||||
    /// Systems that have run but have not had their buffers applied.
 | 
			
		||||
    unapplied_systems: FixedBitSet,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for MultiThreadedExecutor {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self::new()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SystemExecutor for MultiThreadedExecutor {
 | 
			
		||||
    fn kind(&self) -> ExecutorKind {
 | 
			
		||||
        ExecutorKind::MultiThreaded
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn init(&mut self, schedule: &SystemSchedule) {
 | 
			
		||||
        // pre-allocate space
 | 
			
		||||
        let sys_count = schedule.system_ids.len();
 | 
			
		||||
        let set_count = schedule.set_ids.len();
 | 
			
		||||
 | 
			
		||||
        self.evaluated_sets = FixedBitSet::with_capacity(set_count);
 | 
			
		||||
        self.ready_systems = FixedBitSet::with_capacity(sys_count);
 | 
			
		||||
        self.ready_systems_copy = FixedBitSet::with_capacity(sys_count);
 | 
			
		||||
        self.running_systems = FixedBitSet::with_capacity(sys_count);
 | 
			
		||||
        self.completed_systems = FixedBitSet::with_capacity(sys_count);
 | 
			
		||||
        self.skipped_systems = FixedBitSet::with_capacity(sys_count);
 | 
			
		||||
        self.unapplied_systems = FixedBitSet::with_capacity(sys_count);
 | 
			
		||||
 | 
			
		||||
        self.system_task_metadata = Vec::with_capacity(sys_count);
 | 
			
		||||
        for index in 0..sys_count {
 | 
			
		||||
            self.system_task_metadata.push(SystemTaskMetadata {
 | 
			
		||||
                archetype_component_access: default(),
 | 
			
		||||
                dependents: schedule.system_dependents[index].clone(),
 | 
			
		||||
                is_send: schedule.systems[index].is_send(),
 | 
			
		||||
                is_exclusive: schedule.systems[index].is_exclusive(),
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.num_dependencies_remaining = Vec::with_capacity(sys_count);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) {
 | 
			
		||||
        // reset counts
 | 
			
		||||
        let num_systems = schedule.systems.len();
 | 
			
		||||
        self.num_running_systems = 0;
 | 
			
		||||
        self.num_completed_systems = 0;
 | 
			
		||||
        self.num_dependencies_remaining.clear();
 | 
			
		||||
        self.num_dependencies_remaining
 | 
			
		||||
            .extend_from_slice(&schedule.system_dependencies);
 | 
			
		||||
 | 
			
		||||
        for (system_index, dependencies) in self.num_dependencies_remaining.iter_mut().enumerate() {
 | 
			
		||||
            if *dependencies == 0 {
 | 
			
		||||
                self.ready_systems.insert(system_index);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let world = SyncUnsafeCell::from_mut(world);
 | 
			
		||||
        let SyncUnsafeSchedule {
 | 
			
		||||
            systems,
 | 
			
		||||
            mut conditions,
 | 
			
		||||
        } = SyncUnsafeSchedule::new(schedule);
 | 
			
		||||
 | 
			
		||||
        ComputeTaskPool::init(TaskPool::default).scope(|scope| {
 | 
			
		||||
            // the executor itself is a `Send` future so that it can run
 | 
			
		||||
            // alongside systems that claim the local thread
 | 
			
		||||
            let executor = async {
 | 
			
		||||
                while self.num_completed_systems < num_systems {
 | 
			
		||||
                    // SAFETY: self.ready_systems does not contain running systems
 | 
			
		||||
                    unsafe {
 | 
			
		||||
                        self.spawn_system_tasks(scope, systems, &mut conditions, world);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if self.num_running_systems > 0 {
 | 
			
		||||
                        // wait for systems to complete
 | 
			
		||||
                        let index = self
 | 
			
		||||
                            .receiver
 | 
			
		||||
                            .recv()
 | 
			
		||||
                            .await
 | 
			
		||||
                            .unwrap_or_else(|error| unreachable!("{}", error));
 | 
			
		||||
 | 
			
		||||
                        self.finish_system_and_signal_dependents(index);
 | 
			
		||||
 | 
			
		||||
                        while let Ok(index) = self.receiver.try_recv() {
 | 
			
		||||
                            self.finish_system_and_signal_dependents(index);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        self.rebuild_active_access();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // SAFETY: all systems have completed
 | 
			
		||||
                let world = unsafe { &mut *world.get() };
 | 
			
		||||
                apply_system_buffers(&mut self.unapplied_systems, systems, world);
 | 
			
		||||
 | 
			
		||||
                debug_assert!(self.ready_systems.is_clear());
 | 
			
		||||
                debug_assert!(self.running_systems.is_clear());
 | 
			
		||||
                debug_assert!(self.unapplied_systems.is_clear());
 | 
			
		||||
                self.active_access.clear();
 | 
			
		||||
                self.evaluated_sets.clear();
 | 
			
		||||
                self.skipped_systems.clear();
 | 
			
		||||
                self.completed_systems.clear();
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            let executor_span = info_span!("schedule_task");
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            let executor = executor.instrument(executor_span);
 | 
			
		||||
            scope.spawn(executor);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MultiThreadedExecutor {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        let (sender, receiver) = async_channel::unbounded();
 | 
			
		||||
        Self {
 | 
			
		||||
            sender,
 | 
			
		||||
            receiver,
 | 
			
		||||
            system_task_metadata: Vec::new(),
 | 
			
		||||
            num_running_systems: 0,
 | 
			
		||||
            num_completed_systems: 0,
 | 
			
		||||
            num_dependencies_remaining: Vec::new(),
 | 
			
		||||
            active_access: default(),
 | 
			
		||||
            local_thread_running: false,
 | 
			
		||||
            exclusive_running: false,
 | 
			
		||||
            evaluated_sets: FixedBitSet::new(),
 | 
			
		||||
            ready_systems: FixedBitSet::new(),
 | 
			
		||||
            ready_systems_copy: FixedBitSet::new(),
 | 
			
		||||
            running_systems: FixedBitSet::new(),
 | 
			
		||||
            skipped_systems: FixedBitSet::new(),
 | 
			
		||||
            completed_systems: FixedBitSet::new(),
 | 
			
		||||
            unapplied_systems: FixedBitSet::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// # Safety
 | 
			
		||||
    /// Caller must ensure that `self.ready_systems` does not contain any systems that
 | 
			
		||||
    /// have been mutably borrowed (such as the systems currently running).
 | 
			
		||||
    unsafe fn spawn_system_tasks<'scope>(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        scope: &Scope<'_, 'scope, ()>,
 | 
			
		||||
        systems: &'scope [SyncUnsafeCell<BoxedSystem>],
 | 
			
		||||
        conditions: &mut Conditions,
 | 
			
		||||
        cell: &'scope SyncUnsafeCell<World>,
 | 
			
		||||
    ) {
 | 
			
		||||
        if self.exclusive_running {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // can't borrow since loop mutably borrows `self`
 | 
			
		||||
        let mut ready_systems = std::mem::take(&mut self.ready_systems_copy);
 | 
			
		||||
        ready_systems.clear();
 | 
			
		||||
        ready_systems.union_with(&self.ready_systems);
 | 
			
		||||
 | 
			
		||||
        for system_index in ready_systems.ones() {
 | 
			
		||||
            assert!(!self.running_systems.contains(system_index));
 | 
			
		||||
            // SAFETY: Caller assured that these systems are not running.
 | 
			
		||||
            // Therefore, no other reference to this system exists and there is no aliasing.
 | 
			
		||||
            let system = unsafe { &mut *systems[system_index].get() };
 | 
			
		||||
 | 
			
		||||
            // SAFETY: No exclusive system is running.
 | 
			
		||||
            // Therefore, there is no existing mutable reference to the world.
 | 
			
		||||
            let world = unsafe { &*cell.get() };
 | 
			
		||||
            if !self.can_run(system_index, system, conditions, world) {
 | 
			
		||||
                // NOTE: exclusive systems with ambiguities are susceptible to
 | 
			
		||||
                // being significantly displaced here (compared to single-threaded order)
 | 
			
		||||
                // if systems after them in topological order can run
 | 
			
		||||
                // if that becomes an issue, `break;` if exclusive system
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            self.ready_systems.set(system_index, false);
 | 
			
		||||
 | 
			
		||||
            if !self.should_run(system_index, system, conditions, world) {
 | 
			
		||||
                self.skip_system_and_signal_dependents(system_index);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            self.running_systems.insert(system_index);
 | 
			
		||||
            self.num_running_systems += 1;
 | 
			
		||||
 | 
			
		||||
            if self.system_task_metadata[system_index].is_exclusive {
 | 
			
		||||
                // SAFETY: `can_run` confirmed that no systems are running.
 | 
			
		||||
                // Therefore, there is no existing reference to the world.
 | 
			
		||||
                unsafe {
 | 
			
		||||
                    let world = &mut *cell.get();
 | 
			
		||||
                    self.spawn_exclusive_system_task(scope, system_index, systems, world);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // SAFETY: No other reference to this system exists.
 | 
			
		||||
            unsafe {
 | 
			
		||||
                self.spawn_system_task(scope, system_index, systems, world);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // give back
 | 
			
		||||
        self.ready_systems_copy = ready_systems;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn can_run(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        system_index: usize,
 | 
			
		||||
        system: &mut BoxedSystem,
 | 
			
		||||
        conditions: &mut Conditions,
 | 
			
		||||
        world: &World,
 | 
			
		||||
    ) -> bool {
 | 
			
		||||
        #[cfg(feature = "trace")]
 | 
			
		||||
        let _span = info_span!("check_access", name = &*system.name()).entered();
 | 
			
		||||
 | 
			
		||||
        let system_meta = &self.system_task_metadata[system_index];
 | 
			
		||||
        if system_meta.is_exclusive && self.num_running_systems > 0 {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if !system_meta.is_send && self.local_thread_running {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // TODO: an earlier out if world's archetypes did not change
 | 
			
		||||
        for set_idx in conditions.sets_of_systems[system_index].difference(&self.evaluated_sets) {
 | 
			
		||||
            for condition in &mut conditions.set_conditions[set_idx] {
 | 
			
		||||
                condition.update_archetype_component_access(world);
 | 
			
		||||
                if !condition
 | 
			
		||||
                    .archetype_component_access()
 | 
			
		||||
                    .is_compatible(&self.active_access)
 | 
			
		||||
                {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for condition in &mut conditions.system_conditions[system_index] {
 | 
			
		||||
            condition.update_archetype_component_access(world);
 | 
			
		||||
            if !condition
 | 
			
		||||
                .archetype_component_access()
 | 
			
		||||
                .is_compatible(&self.active_access)
 | 
			
		||||
            {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if !self.skipped_systems.contains(system_index) {
 | 
			
		||||
            system.update_archetype_component_access(world);
 | 
			
		||||
            if !system
 | 
			
		||||
                .archetype_component_access()
 | 
			
		||||
                .is_compatible(&self.active_access)
 | 
			
		||||
            {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // PERF: use an optimized clear() + extend() operation
 | 
			
		||||
            let meta_access =
 | 
			
		||||
                &mut self.system_task_metadata[system_index].archetype_component_access;
 | 
			
		||||
            meta_access.clear();
 | 
			
		||||
            meta_access.extend(system.archetype_component_access());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn should_run(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        system_index: usize,
 | 
			
		||||
        _system: &BoxedSystem,
 | 
			
		||||
        conditions: &mut Conditions,
 | 
			
		||||
        world: &World,
 | 
			
		||||
    ) -> bool {
 | 
			
		||||
        #[cfg(feature = "trace")]
 | 
			
		||||
        let _span = info_span!("check_conditions", name = &*_system.name()).entered();
 | 
			
		||||
 | 
			
		||||
        let mut should_run = !self.skipped_systems.contains(system_index);
 | 
			
		||||
        for set_idx in conditions.sets_of_systems[system_index].ones() {
 | 
			
		||||
            if self.evaluated_sets.contains(set_idx) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // evaluate system set's conditions
 | 
			
		||||
            let set_conditions_met =
 | 
			
		||||
                evaluate_and_fold_conditions(&mut conditions.set_conditions[set_idx], world);
 | 
			
		||||
 | 
			
		||||
            if !set_conditions_met {
 | 
			
		||||
                self.skipped_systems
 | 
			
		||||
                    .union_with(&conditions.systems_in_sets[set_idx]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            should_run &= set_conditions_met;
 | 
			
		||||
            self.evaluated_sets.insert(set_idx);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // evaluate system's conditions
 | 
			
		||||
        let system_conditions_met =
 | 
			
		||||
            evaluate_and_fold_conditions(&mut conditions.system_conditions[system_index], world);
 | 
			
		||||
 | 
			
		||||
        if !system_conditions_met {
 | 
			
		||||
            self.skipped_systems.insert(system_index);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        should_run &= system_conditions_met;
 | 
			
		||||
 | 
			
		||||
        should_run
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// # Safety
 | 
			
		||||
    /// Caller must not alias systems that are running.
 | 
			
		||||
    unsafe fn spawn_system_task<'scope>(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        scope: &Scope<'_, 'scope, ()>,
 | 
			
		||||
        system_index: usize,
 | 
			
		||||
        systems: &'scope [SyncUnsafeCell<BoxedSystem>],
 | 
			
		||||
        world: &'scope World,
 | 
			
		||||
    ) {
 | 
			
		||||
        // SAFETY: this system is not running, no other reference exists
 | 
			
		||||
        let system = unsafe { &mut *systems[system_index].get() };
 | 
			
		||||
 | 
			
		||||
        #[cfg(feature = "trace")]
 | 
			
		||||
        let task_span = info_span!("system_task", name = &*system.name());
 | 
			
		||||
        #[cfg(feature = "trace")]
 | 
			
		||||
        let system_span = info_span!("system", name = &*system.name());
 | 
			
		||||
 | 
			
		||||
        let sender = self.sender.clone();
 | 
			
		||||
        let task = async move {
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            let system_guard = system_span.enter();
 | 
			
		||||
            // SAFETY: access is compatible
 | 
			
		||||
            unsafe { system.run_unsafe((), world) };
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            drop(system_guard);
 | 
			
		||||
            sender
 | 
			
		||||
                .send(system_index)
 | 
			
		||||
                .await
 | 
			
		||||
                .unwrap_or_else(|error| unreachable!("{}", error));
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        #[cfg(feature = "trace")]
 | 
			
		||||
        let task = task.instrument(task_span);
 | 
			
		||||
 | 
			
		||||
        let system_meta = &self.system_task_metadata[system_index];
 | 
			
		||||
        self.active_access
 | 
			
		||||
            .extend(&system_meta.archetype_component_access);
 | 
			
		||||
 | 
			
		||||
        if system_meta.is_send {
 | 
			
		||||
            scope.spawn(task);
 | 
			
		||||
        } else {
 | 
			
		||||
            self.local_thread_running = true;
 | 
			
		||||
            scope.spawn_on_scope(task);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// # Safety
 | 
			
		||||
    /// Caller must ensure no systems are currently borrowed.
 | 
			
		||||
    unsafe fn spawn_exclusive_system_task<'scope>(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        scope: &Scope<'_, 'scope, ()>,
 | 
			
		||||
        system_index: usize,
 | 
			
		||||
        systems: &'scope [SyncUnsafeCell<BoxedSystem>],
 | 
			
		||||
        world: &'scope mut World,
 | 
			
		||||
    ) {
 | 
			
		||||
        // SAFETY: this system is not running, no other reference exists
 | 
			
		||||
        let system = unsafe { &mut *systems[system_index].get() };
 | 
			
		||||
 | 
			
		||||
        #[cfg(feature = "trace")]
 | 
			
		||||
        let task_span = info_span!("system_task", name = &*system.name());
 | 
			
		||||
        #[cfg(feature = "trace")]
 | 
			
		||||
        let system_span = info_span!("system", name = &*system.name());
 | 
			
		||||
 | 
			
		||||
        let sender = self.sender.clone();
 | 
			
		||||
        if is_apply_system_buffers(system) {
 | 
			
		||||
            // TODO: avoid allocation
 | 
			
		||||
            let mut unapplied_systems = self.unapplied_systems.clone();
 | 
			
		||||
            let task = async move {
 | 
			
		||||
                #[cfg(feature = "trace")]
 | 
			
		||||
                let system_guard = system_span.enter();
 | 
			
		||||
                apply_system_buffers(&mut unapplied_systems, systems, world);
 | 
			
		||||
                #[cfg(feature = "trace")]
 | 
			
		||||
                drop(system_guard);
 | 
			
		||||
                sender
 | 
			
		||||
                    .send(system_index)
 | 
			
		||||
                    .await
 | 
			
		||||
                    .unwrap_or_else(|error| unreachable!("{}", error));
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            let task = task.instrument(task_span);
 | 
			
		||||
            scope.spawn_on_scope(task);
 | 
			
		||||
        } else {
 | 
			
		||||
            let task = async move {
 | 
			
		||||
                #[cfg(feature = "trace")]
 | 
			
		||||
                let system_guard = system_span.enter();
 | 
			
		||||
                system.run((), world);
 | 
			
		||||
                #[cfg(feature = "trace")]
 | 
			
		||||
                drop(system_guard);
 | 
			
		||||
                sender
 | 
			
		||||
                    .send(system_index)
 | 
			
		||||
                    .await
 | 
			
		||||
                    .unwrap_or_else(|error| unreachable!("{}", error));
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            let task = task.instrument(task_span);
 | 
			
		||||
            scope.spawn_on_scope(task);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.exclusive_running = true;
 | 
			
		||||
        self.local_thread_running = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn finish_system_and_signal_dependents(&mut self, system_index: usize) {
 | 
			
		||||
        if self.system_task_metadata[system_index].is_exclusive {
 | 
			
		||||
            self.exclusive_running = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if !self.system_task_metadata[system_index].is_send {
 | 
			
		||||
            self.local_thread_running = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        debug_assert!(self.num_running_systems >= 1);
 | 
			
		||||
        self.num_running_systems -= 1;
 | 
			
		||||
        self.num_completed_systems += 1;
 | 
			
		||||
        self.running_systems.set(system_index, false);
 | 
			
		||||
        self.completed_systems.insert(system_index);
 | 
			
		||||
        self.unapplied_systems.insert(system_index);
 | 
			
		||||
        self.signal_dependents(system_index);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn skip_system_and_signal_dependents(&mut self, system_index: usize) {
 | 
			
		||||
        self.num_completed_systems += 1;
 | 
			
		||||
        self.completed_systems.insert(system_index);
 | 
			
		||||
        self.signal_dependents(system_index);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn signal_dependents(&mut self, system_index: usize) {
 | 
			
		||||
        #[cfg(feature = "trace")]
 | 
			
		||||
        let _span = info_span!("signal_dependents").entered();
 | 
			
		||||
        for &dep_idx in &self.system_task_metadata[system_index].dependents {
 | 
			
		||||
            let remaining = &mut self.num_dependencies_remaining[dep_idx];
 | 
			
		||||
            debug_assert!(*remaining >= 1);
 | 
			
		||||
            *remaining -= 1;
 | 
			
		||||
            if *remaining == 0 && !self.completed_systems.contains(dep_idx) {
 | 
			
		||||
                self.ready_systems.insert(dep_idx);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn rebuild_active_access(&mut self) {
 | 
			
		||||
        self.active_access.clear();
 | 
			
		||||
        for index in self.running_systems.ones() {
 | 
			
		||||
            let system_meta = &self.system_task_metadata[index];
 | 
			
		||||
            self.active_access
 | 
			
		||||
                .extend(&system_meta.archetype_component_access);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn apply_system_buffers(
 | 
			
		||||
    unapplied_systems: &mut FixedBitSet,
 | 
			
		||||
    systems: &[SyncUnsafeCell<BoxedSystem>],
 | 
			
		||||
    world: &mut World,
 | 
			
		||||
) {
 | 
			
		||||
    for system_index in unapplied_systems.ones() {
 | 
			
		||||
        // SAFETY: none of these systems are running, no other references exist
 | 
			
		||||
        let system = unsafe { &mut *systems[system_index].get() };
 | 
			
		||||
        #[cfg(feature = "trace")]
 | 
			
		||||
        let _apply_buffers_span = info_span!("apply_buffers", name = &*system.name()).entered();
 | 
			
		||||
        system.apply_buffers(world);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    unapplied_systems.clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &World) -> bool {
 | 
			
		||||
    // not short-circuiting is intentional
 | 
			
		||||
    #[allow(clippy::unnecessary_fold)]
 | 
			
		||||
    conditions
 | 
			
		||||
        .iter_mut()
 | 
			
		||||
        .map(|condition| {
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            let _condition_span = info_span!("condition", name = &*condition.name()).entered();
 | 
			
		||||
            // SAFETY: caller ensures system access is compatible
 | 
			
		||||
            unsafe { condition.run_unsafe((), world) }
 | 
			
		||||
        })
 | 
			
		||||
        .fold(true, |acc, res| acc && res)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										111
									
								
								crates/bevy_ecs/src/schedule_v3/executor/simple.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								crates/bevy_ecs/src/schedule_v3/executor/simple.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,111 @@
 | 
			
		||||
#[cfg(feature = "trace")]
 | 
			
		||||
use bevy_utils::tracing::info_span;
 | 
			
		||||
use fixedbitset::FixedBitSet;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    schedule_v3::{BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
 | 
			
		||||
    world::World,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// A variant of [`SingleThreadedExecutor`](crate::schedule_v3::SingleThreadedExecutor) that calls
 | 
			
		||||
/// [`apply_buffers`](crate::system::System::apply_buffers) immediately after running each system.
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
pub struct SimpleExecutor {
 | 
			
		||||
    /// Systems sets whose conditions have been evaluated.
 | 
			
		||||
    evaluated_sets: FixedBitSet,
 | 
			
		||||
    /// Systems that have run or been skipped.
 | 
			
		||||
    completed_systems: FixedBitSet,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SystemExecutor for SimpleExecutor {
 | 
			
		||||
    fn kind(&self) -> ExecutorKind {
 | 
			
		||||
        ExecutorKind::Simple
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn init(&mut self, schedule: &SystemSchedule) {
 | 
			
		||||
        let sys_count = schedule.system_ids.len();
 | 
			
		||||
        let set_count = schedule.set_ids.len();
 | 
			
		||||
        self.evaluated_sets = FixedBitSet::with_capacity(set_count);
 | 
			
		||||
        self.completed_systems = FixedBitSet::with_capacity(sys_count);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) {
 | 
			
		||||
        for system_index in 0..schedule.systems.len() {
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            let name = schedule.systems[system_index].name();
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            let should_run_span = info_span!("check_conditions", name = &*name).entered();
 | 
			
		||||
 | 
			
		||||
            let mut should_run = !self.completed_systems.contains(system_index);
 | 
			
		||||
            for set_idx in schedule.sets_of_systems[system_index].ones() {
 | 
			
		||||
                if self.evaluated_sets.contains(set_idx) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // evaluate system set's conditions
 | 
			
		||||
                let set_conditions_met =
 | 
			
		||||
                    evaluate_and_fold_conditions(&mut schedule.set_conditions[set_idx], world);
 | 
			
		||||
 | 
			
		||||
                if !set_conditions_met {
 | 
			
		||||
                    self.completed_systems
 | 
			
		||||
                        .union_with(&schedule.systems_in_sets[set_idx]);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                should_run &= set_conditions_met;
 | 
			
		||||
                self.evaluated_sets.insert(set_idx);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // evaluate system's conditions
 | 
			
		||||
            let system_conditions_met =
 | 
			
		||||
                evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world);
 | 
			
		||||
 | 
			
		||||
            should_run &= system_conditions_met;
 | 
			
		||||
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            should_run_span.exit();
 | 
			
		||||
 | 
			
		||||
            // system has either been skipped or will run
 | 
			
		||||
            self.completed_systems.insert(system_index);
 | 
			
		||||
 | 
			
		||||
            if !should_run {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let system = &mut schedule.systems[system_index];
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            let system_span = info_span!("system", name = &*name).entered();
 | 
			
		||||
            system.run((), world);
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            system_span.exit();
 | 
			
		||||
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            let _apply_buffers_span = info_span!("apply_buffers", name = &*name).entered();
 | 
			
		||||
            system.apply_buffers(world);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.evaluated_sets.clear();
 | 
			
		||||
        self.completed_systems.clear();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SimpleExecutor {
 | 
			
		||||
    pub const fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            evaluated_sets: FixedBitSet::new(),
 | 
			
		||||
            completed_systems: FixedBitSet::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut World) -> bool {
 | 
			
		||||
    // not short-circuiting is intentional
 | 
			
		||||
    #[allow(clippy::unnecessary_fold)]
 | 
			
		||||
    conditions
 | 
			
		||||
        .iter_mut()
 | 
			
		||||
        .map(|condition| {
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            let _condition_span = info_span!("condition", name = &*condition.name()).entered();
 | 
			
		||||
            condition.run((), world)
 | 
			
		||||
        })
 | 
			
		||||
        .fold(true, |acc, res| acc && res)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										137
									
								
								crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								crates/bevy_ecs/src/schedule_v3/executor/single_threaded.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,137 @@
 | 
			
		||||
#[cfg(feature = "trace")]
 | 
			
		||||
use bevy_utils::tracing::info_span;
 | 
			
		||||
use fixedbitset::FixedBitSet;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    schedule_v3::{
 | 
			
		||||
        is_apply_system_buffers, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule,
 | 
			
		||||
    },
 | 
			
		||||
    world::World,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Runs the schedule using a single thread.
 | 
			
		||||
///
 | 
			
		||||
/// Useful if you're dealing with a single-threaded environment, saving your threads for
 | 
			
		||||
/// other things, or just trying minimize overhead.
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
pub struct SingleThreadedExecutor {
 | 
			
		||||
    /// System sets whose conditions have been evaluated.
 | 
			
		||||
    evaluated_sets: FixedBitSet,
 | 
			
		||||
    /// Systems that have run or been skipped.
 | 
			
		||||
    completed_systems: FixedBitSet,
 | 
			
		||||
    /// Systems that have run but have not had their buffers applied.
 | 
			
		||||
    unapplied_systems: FixedBitSet,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SystemExecutor for SingleThreadedExecutor {
 | 
			
		||||
    fn kind(&self) -> ExecutorKind {
 | 
			
		||||
        ExecutorKind::SingleThreaded
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn init(&mut self, schedule: &SystemSchedule) {
 | 
			
		||||
        // pre-allocate space
 | 
			
		||||
        let sys_count = schedule.system_ids.len();
 | 
			
		||||
        let set_count = schedule.set_ids.len();
 | 
			
		||||
        self.evaluated_sets = FixedBitSet::with_capacity(set_count);
 | 
			
		||||
        self.completed_systems = FixedBitSet::with_capacity(sys_count);
 | 
			
		||||
        self.unapplied_systems = FixedBitSet::with_capacity(sys_count);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) {
 | 
			
		||||
        for system_index in 0..schedule.systems.len() {
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            let name = schedule.systems[system_index].name();
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            let should_run_span = info_span!("check_conditions", name = &*name).entered();
 | 
			
		||||
 | 
			
		||||
            let mut should_run = !self.completed_systems.contains(system_index);
 | 
			
		||||
            for set_idx in schedule.sets_of_systems[system_index].ones() {
 | 
			
		||||
                if self.evaluated_sets.contains(set_idx) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // evaluate system set's conditions
 | 
			
		||||
                let set_conditions_met =
 | 
			
		||||
                    evaluate_and_fold_conditions(&mut schedule.set_conditions[set_idx], world);
 | 
			
		||||
 | 
			
		||||
                if !set_conditions_met {
 | 
			
		||||
                    self.completed_systems
 | 
			
		||||
                        .union_with(&schedule.systems_in_sets[set_idx]);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                should_run &= set_conditions_met;
 | 
			
		||||
                self.evaluated_sets.insert(set_idx);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // evaluate system's conditions
 | 
			
		||||
            let system_conditions_met =
 | 
			
		||||
                evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world);
 | 
			
		||||
 | 
			
		||||
            should_run &= system_conditions_met;
 | 
			
		||||
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            should_run_span.exit();
 | 
			
		||||
 | 
			
		||||
            // system has either been skipped or will run
 | 
			
		||||
            self.completed_systems.insert(system_index);
 | 
			
		||||
 | 
			
		||||
            if !should_run {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let system = &mut schedule.systems[system_index];
 | 
			
		||||
            if is_apply_system_buffers(system) {
 | 
			
		||||
                #[cfg(feature = "trace")]
 | 
			
		||||
                let system_span = info_span!("system", name = &*name).entered();
 | 
			
		||||
                self.apply_system_buffers(schedule, world);
 | 
			
		||||
                #[cfg(feature = "trace")]
 | 
			
		||||
                system_span.exit();
 | 
			
		||||
            } else {
 | 
			
		||||
                #[cfg(feature = "trace")]
 | 
			
		||||
                let system_span = info_span!("system", name = &*name).entered();
 | 
			
		||||
                system.run((), world);
 | 
			
		||||
                #[cfg(feature = "trace")]
 | 
			
		||||
                system_span.exit();
 | 
			
		||||
                self.unapplied_systems.insert(system_index);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.apply_system_buffers(schedule, world);
 | 
			
		||||
        self.evaluated_sets.clear();
 | 
			
		||||
        self.completed_systems.clear();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SingleThreadedExecutor {
 | 
			
		||||
    pub const fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            evaluated_sets: FixedBitSet::new(),
 | 
			
		||||
            completed_systems: FixedBitSet::new(),
 | 
			
		||||
            unapplied_systems: FixedBitSet::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn apply_system_buffers(&mut self, schedule: &mut SystemSchedule, world: &mut World) {
 | 
			
		||||
        for system_index in self.unapplied_systems.ones() {
 | 
			
		||||
            let system = &mut schedule.systems[system_index];
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            let _apply_buffers_span = info_span!("apply_buffers", name = &*system.name()).entered();
 | 
			
		||||
            system.apply_buffers(world);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.unapplied_systems.clear();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut World) -> bool {
 | 
			
		||||
    // not short-circuiting is intentional
 | 
			
		||||
    #[allow(clippy::unnecessary_fold)]
 | 
			
		||||
    conditions
 | 
			
		||||
        .iter_mut()
 | 
			
		||||
        .map(|condition| {
 | 
			
		||||
            #[cfg(feature = "trace")]
 | 
			
		||||
            let _condition_span = info_span!("condition", name = &*condition.name()).entered();
 | 
			
		||||
            condition.run((), world)
 | 
			
		||||
        })
 | 
			
		||||
        .fold(true, |acc, res| acc && res)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										233
									
								
								crates/bevy_ecs/src/schedule_v3/graph_utils.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								crates/bevy_ecs/src/schedule_v3/graph_utils.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,233 @@
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
 | 
			
		||||
use bevy_utils::{
 | 
			
		||||
    petgraph::{graphmap::NodeTrait, prelude::*},
 | 
			
		||||
    HashMap, HashSet,
 | 
			
		||||
};
 | 
			
		||||
use fixedbitset::FixedBitSet;
 | 
			
		||||
 | 
			
		||||
use crate::schedule_v3::set::*;
 | 
			
		||||
 | 
			
		||||
/// Unique identifier for a system or system set.
 | 
			
		||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
 | 
			
		||||
pub(crate) enum NodeId {
 | 
			
		||||
    System(usize),
 | 
			
		||||
    Set(usize),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NodeId {
 | 
			
		||||
    /// Returns the internal integer value.
 | 
			
		||||
    pub fn index(&self) -> usize {
 | 
			
		||||
        match self {
 | 
			
		||||
            NodeId::System(index) | NodeId::Set(index) => *index,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Returns `true` if the identified node is a system.
 | 
			
		||||
    pub const fn is_system(&self) -> bool {
 | 
			
		||||
        matches!(self, NodeId::System(_))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Returns `true` if the identified node is a system set.
 | 
			
		||||
    pub const fn is_set(&self) -> bool {
 | 
			
		||||
        matches!(self, NodeId::Set(_))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Specifies what kind of edge should be added to the dependency graph.
 | 
			
		||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)]
 | 
			
		||||
pub(crate) enum DependencyKind {
 | 
			
		||||
    /// A node that should be preceded.
 | 
			
		||||
    Before,
 | 
			
		||||
    /// A node that should be succeeded.
 | 
			
		||||
    After,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// An edge to be added to the dependency graph.
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub(crate) struct Dependency {
 | 
			
		||||
    pub(crate) kind: DependencyKind,
 | 
			
		||||
    pub(crate) set: BoxedSystemSet,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Dependency {
 | 
			
		||||
    pub fn new(kind: DependencyKind, set: BoxedSystemSet) -> Self {
 | 
			
		||||
        Self { kind, set }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Configures ambiguity detection for a single system.
 | 
			
		||||
#[derive(Clone, Debug, Default)]
 | 
			
		||||
pub(crate) enum Ambiguity {
 | 
			
		||||
    #[default]
 | 
			
		||||
    Check,
 | 
			
		||||
    /// Ignore warnings with systems in any of these system sets. May contain duplicates.
 | 
			
		||||
    IgnoreWithSet(Vec<BoxedSystemSet>),
 | 
			
		||||
    /// Ignore all warnings.
 | 
			
		||||
    IgnoreAll,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub(crate) struct GraphInfo {
 | 
			
		||||
    pub(crate) sets: Vec<BoxedSystemSet>,
 | 
			
		||||
    pub(crate) dependencies: Vec<Dependency>,
 | 
			
		||||
    pub(crate) ambiguous_with: Ambiguity,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Converts 2D row-major pair of indices into a 1D array index.
 | 
			
		||||
pub(crate) fn index(row: usize, col: usize, num_cols: usize) -> usize {
 | 
			
		||||
    debug_assert!(col < num_cols);
 | 
			
		||||
    (row * num_cols) + col
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Converts a 1D array index into a 2D row-major pair of indices.
 | 
			
		||||
pub(crate) fn row_col(index: usize, num_cols: usize) -> (usize, usize) {
 | 
			
		||||
    (index / num_cols, index % num_cols)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Stores the results of the graph analysis.
 | 
			
		||||
pub(crate) struct CheckGraphResults<V> {
 | 
			
		||||
    /// Boolean reachability matrix for the graph.
 | 
			
		||||
    pub(crate) reachable: FixedBitSet,
 | 
			
		||||
    /// Pairs of nodes that have a path connecting them.
 | 
			
		||||
    pub(crate) connected: HashSet<(V, V)>,
 | 
			
		||||
    /// Pairs of nodes that don't have a path connecting them.
 | 
			
		||||
    pub(crate) disconnected: HashSet<(V, V)>,
 | 
			
		||||
    /// Edges that are redundant because a longer path exists.
 | 
			
		||||
    pub(crate) transitive_edges: Vec<(V, V)>,
 | 
			
		||||
    /// Variant of the graph with no transitive edges.
 | 
			
		||||
    pub(crate) transitive_reduction: DiGraphMap<V, ()>,
 | 
			
		||||
    /// 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<V, ()>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<V: NodeTrait + Debug> Default for CheckGraphResults<V> {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            reachable: FixedBitSet::new(),
 | 
			
		||||
            connected: HashSet::new(),
 | 
			
		||||
            disconnected: HashSet::new(),
 | 
			
		||||
            transitive_edges: Vec::new(),
 | 
			
		||||
            transitive_reduction: DiGraphMap::new(),
 | 
			
		||||
            transitive_closure: DiGraphMap::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Processes a DAG and computes its:
 | 
			
		||||
/// - transitive reduction (along with the set of removed edges)
 | 
			
		||||
/// - transitive closure
 | 
			
		||||
/// - reachability matrix (as a bitset)
 | 
			
		||||
/// - pairs of nodes connected by a path
 | 
			
		||||
/// - pairs of nodes not connected by a path
 | 
			
		||||
///
 | 
			
		||||
/// The algorithm implemented comes from
 | 
			
		||||
/// ["On the calculation of transitive reduction-closure of orders"][1] by Habib, Morvan and Rampon.
 | 
			
		||||
///
 | 
			
		||||
/// [1]: https://doi.org/10.1016/0012-365X(93)90164-O
 | 
			
		||||
pub(crate) fn check_graph<V>(
 | 
			
		||||
    graph: &DiGraphMap<V, ()>,
 | 
			
		||||
    topological_order: &[V],
 | 
			
		||||
) -> CheckGraphResults<V>
 | 
			
		||||
where
 | 
			
		||||
    V: NodeTrait + Debug,
 | 
			
		||||
{
 | 
			
		||||
    if graph.node_count() == 0 {
 | 
			
		||||
        return CheckGraphResults::default();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let n = graph.node_count();
 | 
			
		||||
 | 
			
		||||
    // build a copy of the graph where the nodes and edges appear in topsorted order
 | 
			
		||||
    let mut map = HashMap::with_capacity(n);
 | 
			
		||||
    let mut topsorted = DiGraphMap::<V, ()>::new();
 | 
			
		||||
    // iterate nodes in topological order
 | 
			
		||||
    for (i, &node) in topological_order.iter().enumerate() {
 | 
			
		||||
        map.insert(node, i);
 | 
			
		||||
        topsorted.add_node(node);
 | 
			
		||||
        // insert nodes as successors to their predecessors
 | 
			
		||||
        for pred in graph.neighbors_directed(node, Direction::Incoming) {
 | 
			
		||||
            topsorted.add_edge(pred, node, ());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let mut reachable = FixedBitSet::with_capacity(n * n);
 | 
			
		||||
    let mut connected = HashSet::new();
 | 
			
		||||
    let mut disconnected = HashSet::new();
 | 
			
		||||
 | 
			
		||||
    let mut transitive_edges = Vec::new();
 | 
			
		||||
    let mut transitive_reduction = DiGraphMap::<V, ()>::new();
 | 
			
		||||
    let mut transitive_closure = DiGraphMap::<V, ()>::new();
 | 
			
		||||
 | 
			
		||||
    let mut visited = FixedBitSet::with_capacity(n);
 | 
			
		||||
 | 
			
		||||
    // iterate nodes in topological order
 | 
			
		||||
    for node in topsorted.nodes() {
 | 
			
		||||
        transitive_reduction.add_node(node);
 | 
			
		||||
        transitive_closure.add_node(node);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // iterate nodes in reverse topological order
 | 
			
		||||
    for a in topsorted.nodes().rev() {
 | 
			
		||||
        let index_a = *map.get(&a).unwrap();
 | 
			
		||||
        // iterate their successors in topological order
 | 
			
		||||
        for b in topsorted.neighbors_directed(a, Direction::Outgoing) {
 | 
			
		||||
            let index_b = *map.get(&b).unwrap();
 | 
			
		||||
            debug_assert!(index_a < index_b);
 | 
			
		||||
            if !visited[index_b] {
 | 
			
		||||
                // edge <a, b> is not redundant
 | 
			
		||||
                transitive_reduction.add_edge(a, b, ());
 | 
			
		||||
                transitive_closure.add_edge(a, b, ());
 | 
			
		||||
                reachable.insert(index(index_a, index_b, n));
 | 
			
		||||
 | 
			
		||||
                let successors = transitive_closure
 | 
			
		||||
                    .neighbors_directed(b, Direction::Outgoing)
 | 
			
		||||
                    .collect::<Vec<_>>();
 | 
			
		||||
                for c in successors {
 | 
			
		||||
                    let index_c = *map.get(&c).unwrap();
 | 
			
		||||
                    debug_assert!(index_b < index_c);
 | 
			
		||||
                    if !visited[index_c] {
 | 
			
		||||
                        visited.insert(index_c);
 | 
			
		||||
                        transitive_closure.add_edge(a, c, ());
 | 
			
		||||
                        reachable.insert(index(index_a, index_c, n));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // edge <a, b> is redundant
 | 
			
		||||
                transitive_edges.push((a, b));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        visited.clear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // partition pairs of nodes into "connected by path" and "not connected by path"
 | 
			
		||||
    for i in 0..(n - 1) {
 | 
			
		||||
        // reachable is upper triangular because the nodes were topsorted
 | 
			
		||||
        for index in index(i, i + 1, n)..=index(i, n - 1, n) {
 | 
			
		||||
            let (a, b) = row_col(index, n);
 | 
			
		||||
            let pair = (topological_order[a], topological_order[b]);
 | 
			
		||||
            if reachable[index] {
 | 
			
		||||
                connected.insert(pair);
 | 
			
		||||
            } else {
 | 
			
		||||
                disconnected.insert(pair);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // fill diagonal (nodes reach themselves)
 | 
			
		||||
    // for i in 0..n {
 | 
			
		||||
    //     reachable.set(index(i, i, n), true);
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    CheckGraphResults {
 | 
			
		||||
        reachable,
 | 
			
		||||
        connected,
 | 
			
		||||
        disconnected,
 | 
			
		||||
        transitive_edges,
 | 
			
		||||
        transitive_reduction,
 | 
			
		||||
        transitive_closure,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								crates/bevy_ecs/src/schedule_v3/migration.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								crates/bevy_ecs/src/schedule_v3/migration.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
use crate::schedule_v3::*;
 | 
			
		||||
use crate::world::World;
 | 
			
		||||
 | 
			
		||||
/// Temporary "stageless" `App` methods.
 | 
			
		||||
pub trait AppExt {
 | 
			
		||||
    /// Sets the [`Schedule`] that will be modified by default when you call `App::add_system`
 | 
			
		||||
    /// and similar methods.
 | 
			
		||||
    ///
 | 
			
		||||
    /// **Note:** This will create the schedule if it does not already exist.
 | 
			
		||||
    fn set_default_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self;
 | 
			
		||||
    /// Applies the function to the [`Schedule`] associated with `label`.
 | 
			
		||||
    ///
 | 
			
		||||
    /// **Note:** This will create the schedule if it does not already exist.
 | 
			
		||||
    fn edit_schedule(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        label: impl ScheduleLabel,
 | 
			
		||||
        f: impl FnMut(&mut Schedule),
 | 
			
		||||
    ) -> &mut Self;
 | 
			
		||||
    /// Adds [`State<S>`] and [`NextState<S>`] resources, [`OnEnter`] and [`OnExit`] schedules
 | 
			
		||||
    /// for each state variant, and an instance of [`apply_state_transition::<S>`] in
 | 
			
		||||
    /// \<insert-`bevy_core`-set-name\> so that transitions happen before `Update`.
 | 
			
		||||
    fn add_state<S: States>(&mut self) -> &mut Self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Temporary "stageless" [`World`] methods.
 | 
			
		||||
pub trait WorldExt {
 | 
			
		||||
    /// Runs the [`Schedule`] associated with `label`.
 | 
			
		||||
    fn run_schedule(&mut self, label: impl ScheduleLabel);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl WorldExt for World {
 | 
			
		||||
    fn run_schedule(&mut self, label: impl ScheduleLabel) {
 | 
			
		||||
        if let Some(mut schedule) = self.resource_mut::<Schedules>().remove(&label) {
 | 
			
		||||
            schedule.run(self);
 | 
			
		||||
            self.resource_mut::<Schedules>().insert(label, schedule);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										471
									
								
								crates/bevy_ecs/src/schedule_v3/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										471
									
								
								crates/bevy_ecs/src/schedule_v3/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,471 @@
 | 
			
		||||
mod condition;
 | 
			
		||||
mod config;
 | 
			
		||||
mod executor;
 | 
			
		||||
mod graph_utils;
 | 
			
		||||
mod migration;
 | 
			
		||||
mod schedule;
 | 
			
		||||
mod set;
 | 
			
		||||
mod state;
 | 
			
		||||
 | 
			
		||||
pub use self::condition::*;
 | 
			
		||||
pub use self::config::*;
 | 
			
		||||
pub use self::executor::*;
 | 
			
		||||
use self::graph_utils::*;
 | 
			
		||||
pub use self::migration::*;
 | 
			
		||||
pub use self::schedule::*;
 | 
			
		||||
pub use self::set::*;
 | 
			
		||||
pub use self::state::*;
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use std::sync::atomic::{AtomicU32, Ordering};
 | 
			
		||||
 | 
			
		||||
    pub use crate as bevy_ecs;
 | 
			
		||||
    pub use crate::schedule_v3::{IntoSystemConfig, IntoSystemSetConfig, Schedule, SystemSet};
 | 
			
		||||
    pub use crate::system::{Res, ResMut};
 | 
			
		||||
    pub use crate::{prelude::World, system::Resource};
 | 
			
		||||
 | 
			
		||||
    #[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
 | 
			
		||||
    enum TestSet {
 | 
			
		||||
        A,
 | 
			
		||||
        B,
 | 
			
		||||
        C,
 | 
			
		||||
        D,
 | 
			
		||||
        X,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(Resource, Default)]
 | 
			
		||||
    struct SystemOrder(Vec<u32>);
 | 
			
		||||
 | 
			
		||||
    #[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::<SystemOrder>().0.push(tag)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn make_function_system(tag: u32) -> impl FnMut(ResMut<SystemOrder>) {
 | 
			
		||||
        move |mut resource: ResMut<SystemOrder>| resource.0.push(tag)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn named_system(mut resource: ResMut<SystemOrder>) {
 | 
			
		||||
        resource.0.push(u32::MAX);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn named_exclusive_system(world: &mut World) {
 | 
			
		||||
        world.resource_mut::<SystemOrder>().0.push(u32::MAX);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn counting_system(counter: Res<Counter>) {
 | 
			
		||||
        counter.0.fetch_add(1, Ordering::Relaxed);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mod system_execution {
 | 
			
		||||
        use super::*;
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        fn run_system() {
 | 
			
		||||
            let mut world = World::default();
 | 
			
		||||
            let mut schedule = Schedule::default();
 | 
			
		||||
 | 
			
		||||
            world.init_resource::<SystemOrder>();
 | 
			
		||||
 | 
			
		||||
            schedule.add_system(make_function_system(0));
 | 
			
		||||
            schedule.run(&mut world);
 | 
			
		||||
 | 
			
		||||
            assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        fn run_exclusive_system() {
 | 
			
		||||
            let mut world = World::default();
 | 
			
		||||
            let mut schedule = Schedule::default();
 | 
			
		||||
 | 
			
		||||
            world.init_resource::<SystemOrder>();
 | 
			
		||||
 | 
			
		||||
            schedule.add_system(make_exclusive_system(0));
 | 
			
		||||
            schedule.run(&mut world);
 | 
			
		||||
 | 
			
		||||
            assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        #[cfg(not(miri))]
 | 
			
		||||
        fn parallel_execution() {
 | 
			
		||||
            use bevy_tasks::{ComputeTaskPool, TaskPool};
 | 
			
		||||
            use std::sync::{Arc, Barrier};
 | 
			
		||||
 | 
			
		||||
            let mut world = World::default();
 | 
			
		||||
            let mut schedule = Schedule::default();
 | 
			
		||||
            let thread_count = ComputeTaskPool::init(TaskPool::default).thread_num();
 | 
			
		||||
 | 
			
		||||
            let barrier = Arc::new(Barrier::new(thread_count));
 | 
			
		||||
 | 
			
		||||
            for _ in 0..thread_count {
 | 
			
		||||
                let inner = barrier.clone();
 | 
			
		||||
                schedule.add_system(move || {
 | 
			
		||||
                    inner.wait();
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            schedule.run(&mut world);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mod system_ordering {
 | 
			
		||||
        use super::*;
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        fn order_systems() {
 | 
			
		||||
            let mut world = World::default();
 | 
			
		||||
            let mut schedule = Schedule::default();
 | 
			
		||||
 | 
			
		||||
            world.init_resource::<SystemOrder>();
 | 
			
		||||
 | 
			
		||||
            schedule.add_system(named_system);
 | 
			
		||||
            schedule.add_system(make_function_system(1).before(named_system));
 | 
			
		||||
            schedule.add_system(
 | 
			
		||||
                make_function_system(0)
 | 
			
		||||
                    .after(named_system)
 | 
			
		||||
                    .in_set(TestSet::A),
 | 
			
		||||
            );
 | 
			
		||||
            schedule.run(&mut world);
 | 
			
		||||
 | 
			
		||||
            assert_eq!(world.resource::<SystemOrder>().0, vec![1, u32::MAX, 0]);
 | 
			
		||||
 | 
			
		||||
            world.insert_resource(SystemOrder::default());
 | 
			
		||||
 | 
			
		||||
            assert_eq!(world.resource::<SystemOrder>().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::<SystemOrder>().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::<SystemOrder>();
 | 
			
		||||
 | 
			
		||||
            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::<SystemOrder>().0, vec![1, u32::MAX, 0]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        fn add_systems_correct_order() {
 | 
			
		||||
            #[derive(Resource)]
 | 
			
		||||
            struct X(Vec<TestSet>);
 | 
			
		||||
 | 
			
		||||
            let mut world = World::new();
 | 
			
		||||
            world.init_resource::<SystemOrder>();
 | 
			
		||||
 | 
			
		||||
            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::<SystemOrder>().0, vec![0, 1, 2, 3]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mod conditions {
 | 
			
		||||
        use super::*;
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        fn system_with_condition() {
 | 
			
		||||
            let mut world = World::default();
 | 
			
		||||
            let mut schedule = Schedule::default();
 | 
			
		||||
 | 
			
		||||
            world.init_resource::<RunConditionBool>();
 | 
			
		||||
            world.init_resource::<SystemOrder>();
 | 
			
		||||
 | 
			
		||||
            schedule.add_system(
 | 
			
		||||
                make_function_system(0).run_if(|condition: Res<RunConditionBool>| condition.0),
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            schedule.run(&mut world);
 | 
			
		||||
            assert_eq!(world.resource::<SystemOrder>().0, vec![]);
 | 
			
		||||
 | 
			
		||||
            world.resource_mut::<RunConditionBool>().0 = true;
 | 
			
		||||
            schedule.run(&mut world);
 | 
			
		||||
            assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        fn run_exclusive_system_with_condition() {
 | 
			
		||||
            let mut world = World::default();
 | 
			
		||||
            let mut schedule = Schedule::default();
 | 
			
		||||
 | 
			
		||||
            world.init_resource::<RunConditionBool>();
 | 
			
		||||
            world.init_resource::<SystemOrder>();
 | 
			
		||||
 | 
			
		||||
            schedule.add_system(
 | 
			
		||||
                make_exclusive_system(0).run_if(|condition: Res<RunConditionBool>| condition.0),
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            schedule.run(&mut world);
 | 
			
		||||
            assert_eq!(world.resource::<SystemOrder>().0, vec![]);
 | 
			
		||||
 | 
			
		||||
            world.resource_mut::<RunConditionBool>().0 = true;
 | 
			
		||||
            schedule.run(&mut world);
 | 
			
		||||
            assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        fn multiple_conditions_on_system() {
 | 
			
		||||
            let mut world = World::default();
 | 
			
		||||
            let mut schedule = Schedule::default();
 | 
			
		||||
 | 
			
		||||
            world.init_resource::<Counter>();
 | 
			
		||||
 | 
			
		||||
            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::<Counter>().0.load(Ordering::Relaxed), 1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        fn multiple_conditions_on_system_sets() {
 | 
			
		||||
            let mut world = World::default();
 | 
			
		||||
            let mut schedule = Schedule::default();
 | 
			
		||||
 | 
			
		||||
            world.init_resource::<Counter>();
 | 
			
		||||
 | 
			
		||||
            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::<Counter>().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::<Counter>();
 | 
			
		||||
 | 
			
		||||
            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::<Counter>().0.load(Ordering::Relaxed), 1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mod schedule_build_errors {
 | 
			
		||||
        use super::*;
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        #[should_panic]
 | 
			
		||||
        fn dependency_loop() {
 | 
			
		||||
            let mut schedule = Schedule::new();
 | 
			
		||||
            schedule.configure_set(TestSet::X.after(TestSet::X));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        fn dependency_cycle() {
 | 
			
		||||
            let mut world = World::new();
 | 
			
		||||
            let mut schedule = Schedule::new();
 | 
			
		||||
 | 
			
		||||
            schedule.configure_set(TestSet::A.after(TestSet::B));
 | 
			
		||||
            schedule.configure_set(TestSet::B.after(TestSet::A));
 | 
			
		||||
 | 
			
		||||
            let result = schedule.initialize(&mut world);
 | 
			
		||||
            assert!(matches!(result, Err(ScheduleBuildError::DependencyCycle)));
 | 
			
		||||
 | 
			
		||||
            fn foo() {}
 | 
			
		||||
            fn bar() {}
 | 
			
		||||
 | 
			
		||||
            let mut world = World::new();
 | 
			
		||||
            let mut schedule = Schedule::new();
 | 
			
		||||
 | 
			
		||||
            schedule.add_systems((foo.after(bar), bar.after(foo)));
 | 
			
		||||
            let result = schedule.initialize(&mut world);
 | 
			
		||||
            assert!(matches!(result, Err(ScheduleBuildError::DependencyCycle)));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        #[should_panic]
 | 
			
		||||
        fn hierarchy_loop() {
 | 
			
		||||
            let mut schedule = Schedule::new();
 | 
			
		||||
            schedule.configure_set(TestSet::X.in_set(TestSet::X));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        fn hierarchy_cycle() {
 | 
			
		||||
            let mut world = World::new();
 | 
			
		||||
            let mut schedule = Schedule::new();
 | 
			
		||||
 | 
			
		||||
            schedule.configure_set(TestSet::A.in_set(TestSet::B));
 | 
			
		||||
            schedule.configure_set(TestSet::B.in_set(TestSet::A));
 | 
			
		||||
 | 
			
		||||
            let result = schedule.initialize(&mut world);
 | 
			
		||||
            assert!(matches!(result, Err(ScheduleBuildError::HierarchyCycle)));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        fn system_type_set_ambiguity() {
 | 
			
		||||
            // Define some systems.
 | 
			
		||||
            fn foo() {}
 | 
			
		||||
            fn bar() {}
 | 
			
		||||
 | 
			
		||||
            let mut world = World::new();
 | 
			
		||||
            let mut schedule = Schedule::new();
 | 
			
		||||
 | 
			
		||||
            // Schedule `bar` to run after `foo`.
 | 
			
		||||
            schedule.add_system(foo);
 | 
			
		||||
            schedule.add_system(bar.after(foo));
 | 
			
		||||
 | 
			
		||||
            // There's only one `foo`, so it's fine.
 | 
			
		||||
            let result = schedule.initialize(&mut world);
 | 
			
		||||
            assert!(result.is_ok());
 | 
			
		||||
 | 
			
		||||
            // Schedule another `foo`.
 | 
			
		||||
            schedule.add_system(foo);
 | 
			
		||||
 | 
			
		||||
            // When there are multiple instances of `foo`, dependencies on
 | 
			
		||||
            // `foo` are no longer allowed. Too much ambiguity.
 | 
			
		||||
            let result = schedule.initialize(&mut world);
 | 
			
		||||
            assert!(matches!(
 | 
			
		||||
                result,
 | 
			
		||||
                Err(ScheduleBuildError::SystemTypeSetAmbiguity(_))
 | 
			
		||||
            ));
 | 
			
		||||
 | 
			
		||||
            // same goes for `ambiguous_with`
 | 
			
		||||
            let mut schedule = Schedule::new();
 | 
			
		||||
            schedule.add_system(foo);
 | 
			
		||||
            schedule.add_system(bar.ambiguous_with(foo));
 | 
			
		||||
            let result = schedule.initialize(&mut world);
 | 
			
		||||
            assert!(result.is_ok());
 | 
			
		||||
            schedule.add_system(foo);
 | 
			
		||||
            let result = schedule.initialize(&mut world);
 | 
			
		||||
            assert!(matches!(
 | 
			
		||||
                result,
 | 
			
		||||
                Err(ScheduleBuildError::SystemTypeSetAmbiguity(_))
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        #[should_panic]
 | 
			
		||||
        fn in_system_type_set() {
 | 
			
		||||
            fn foo() {}
 | 
			
		||||
            fn bar() {}
 | 
			
		||||
 | 
			
		||||
            let mut schedule = Schedule::new();
 | 
			
		||||
            schedule.add_system(foo.in_set(bar.into_system_set()));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        #[should_panic]
 | 
			
		||||
        fn configure_system_type_set() {
 | 
			
		||||
            fn foo() {}
 | 
			
		||||
            let mut schedule = Schedule::new();
 | 
			
		||||
            schedule.configure_set(foo.into_system_set());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        fn hierarchy_redundancy() {
 | 
			
		||||
            let mut world = World::new();
 | 
			
		||||
            let mut schedule = Schedule::new();
 | 
			
		||||
 | 
			
		||||
            schedule.set_build_settings(
 | 
			
		||||
                ScheduleBuildSettings::new().with_hierarchy_detection(LogLevel::Error),
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // Add `A`.
 | 
			
		||||
            schedule.configure_set(TestSet::A);
 | 
			
		||||
 | 
			
		||||
            // Add `B` as child of `A`.
 | 
			
		||||
            schedule.configure_set(TestSet::B.in_set(TestSet::A));
 | 
			
		||||
 | 
			
		||||
            // Add `X` as child of both `A` and `B`.
 | 
			
		||||
            schedule.configure_set(TestSet::X.in_set(TestSet::A).in_set(TestSet::B));
 | 
			
		||||
 | 
			
		||||
            // `X` cannot be the `A`'s child and grandchild at the same time.
 | 
			
		||||
            let result = schedule.initialize(&mut world);
 | 
			
		||||
            assert!(matches!(
 | 
			
		||||
                result,
 | 
			
		||||
                Err(ScheduleBuildError::HierarchyRedundancy)
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        fn cross_dependency() {
 | 
			
		||||
            let mut world = World::new();
 | 
			
		||||
            let mut schedule = Schedule::new();
 | 
			
		||||
 | 
			
		||||
            // Add `B` and give it both kinds of relationships with `A`.
 | 
			
		||||
            schedule.configure_set(TestSet::B.in_set(TestSet::A));
 | 
			
		||||
            schedule.configure_set(TestSet::B.after(TestSet::A));
 | 
			
		||||
            let result = schedule.initialize(&mut world);
 | 
			
		||||
            assert!(matches!(
 | 
			
		||||
                result,
 | 
			
		||||
                Err(ScheduleBuildError::CrossDependency(_, _))
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[test]
 | 
			
		||||
        fn ambiguity() {
 | 
			
		||||
            #[derive(Resource)]
 | 
			
		||||
            struct X;
 | 
			
		||||
 | 
			
		||||
            fn res_ref(_x: Res<X>) {}
 | 
			
		||||
            fn res_mut(_x: ResMut<X>) {}
 | 
			
		||||
 | 
			
		||||
            let mut world = World::new();
 | 
			
		||||
            let mut schedule = Schedule::new();
 | 
			
		||||
 | 
			
		||||
            schedule.set_build_settings(
 | 
			
		||||
                ScheduleBuildSettings::new().with_ambiguity_detection(LogLevel::Error),
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            schedule.add_systems((res_ref, res_mut));
 | 
			
		||||
            let result = schedule.initialize(&mut world);
 | 
			
		||||
            assert!(matches!(result, Err(ScheduleBuildError::Ambiguity)));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1099
									
								
								crates/bevy_ecs/src/schedule_v3/schedule.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1099
									
								
								crates/bevy_ecs/src/schedule_v3/schedule.rs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										149
									
								
								crates/bevy_ecs/src/schedule_v3/set.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								crates/bevy_ecs/src/schedule_v3/set.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,149 @@
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
use std::hash::{Hash, Hasher};
 | 
			
		||||
use std::marker::PhantomData;
 | 
			
		||||
 | 
			
		||||
pub use bevy_ecs_macros::{ScheduleLabel, SystemSet};
 | 
			
		||||
use bevy_utils::define_boxed_label;
 | 
			
		||||
use bevy_utils::label::DynHash;
 | 
			
		||||
 | 
			
		||||
use crate::system::{
 | 
			
		||||
    ExclusiveSystemParam, ExclusiveSystemParamFunction, IsExclusiveFunctionSystem,
 | 
			
		||||
    IsFunctionSystem, SystemParam, SystemParamFunction,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
define_boxed_label!(ScheduleLabel);
 | 
			
		||||
 | 
			
		||||
pub type BoxedSystemSet = Box<dyn SystemSet>;
 | 
			
		||||
pub type BoxedScheduleLabel = Box<dyn ScheduleLabel>;
 | 
			
		||||
 | 
			
		||||
/// 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<dyn SystemSet>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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<H: Hasher>(&self, state: &mut H) {
 | 
			
		||||
        self.dyn_hash(state);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Clone for Box<dyn SystemSet> {
 | 
			
		||||
    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<T: 'static>(PhantomData<fn() -> T>);
 | 
			
		||||
 | 
			
		||||
impl<T: 'static> SystemTypeSet<T> {
 | 
			
		||||
    pub(crate) fn new() -> Self {
 | 
			
		||||
        Self(PhantomData)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T> Debug for SystemTypeSet<T> {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        f.debug_tuple("SystemTypeSet")
 | 
			
		||||
            .field(&std::any::type_name::<T>())
 | 
			
		||||
            .finish()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T> Hash for SystemTypeSet<T> {
 | 
			
		||||
    fn hash<H: Hasher>(&self, _state: &mut H) {
 | 
			
		||||
        // all systems of a given type are the same
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
impl<T> Clone for SystemTypeSet<T> {
 | 
			
		||||
    fn clone(&self) -> Self {
 | 
			
		||||
        Self(PhantomData)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T> Copy for SystemTypeSet<T> {}
 | 
			
		||||
 | 
			
		||||
impl<T> PartialEq for SystemTypeSet<T> {
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn eq(&self, _other: &Self) -> bool {
 | 
			
		||||
        // all systems of a given type are the same
 | 
			
		||||
        true
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T> Eq for SystemTypeSet<T> {}
 | 
			
		||||
 | 
			
		||||
impl<T> SystemSet for SystemTypeSet<T> {
 | 
			
		||||
    fn is_system_type(&self) -> bool {
 | 
			
		||||
        true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn dyn_clone(&self) -> Box<dyn SystemSet> {
 | 
			
		||||
        Box::new(*self)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Types that can be converted into a [`SystemSet`].
 | 
			
		||||
pub trait IntoSystemSet<Marker>: Sized {
 | 
			
		||||
    type Set: SystemSet;
 | 
			
		||||
 | 
			
		||||
    fn into_system_set(self) -> Self::Set;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// systems sets
 | 
			
		||||
impl<S: SystemSet> IntoSystemSet<()> for S {
 | 
			
		||||
    type Set = Self;
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn into_system_set(self) -> Self::Set {
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// systems
 | 
			
		||||
impl<In, Out, Param, Marker, F> IntoSystemSet<(IsFunctionSystem, In, Out, Param, Marker)> for F
 | 
			
		||||
where
 | 
			
		||||
    Param: SystemParam,
 | 
			
		||||
    F: SystemParamFunction<In, Out, Param, Marker>,
 | 
			
		||||
{
 | 
			
		||||
    type Set = SystemTypeSet<Self>;
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn into_system_set(self) -> Self::Set {
 | 
			
		||||
        SystemTypeSet::new()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// exclusive systems
 | 
			
		||||
impl<In, Out, Param, Marker, F> IntoSystemSet<(IsExclusiveFunctionSystem, In, Out, Param, Marker)>
 | 
			
		||||
    for F
 | 
			
		||||
where
 | 
			
		||||
    Param: ExclusiveSystemParam,
 | 
			
		||||
    F: ExclusiveSystemParamFunction<In, Out, Param, Marker>,
 | 
			
		||||
{
 | 
			
		||||
    type Set = SystemTypeSet<Self>;
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn into_system_set(self) -> Self::Set {
 | 
			
		||||
        SystemTypeSet::new()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								crates/bevy_ecs/src/schedule_v3/state.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								crates/bevy_ecs/src/schedule_v3/state.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
use std::hash::Hash;
 | 
			
		||||
use std::mem;
 | 
			
		||||
 | 
			
		||||
use crate as bevy_ecs;
 | 
			
		||||
use crate::schedule_v3::{ScheduleLabel, SystemSet, WorldExt};
 | 
			
		||||
use crate::system::Resource;
 | 
			
		||||
use crate::world::World;
 | 
			
		||||
 | 
			
		||||
/// Types that can define states in a finite-state machine.
 | 
			
		||||
pub trait States: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug {
 | 
			
		||||
    type Iter: Iterator<Item = Self>;
 | 
			
		||||
 | 
			
		||||
    /// Returns an iterator over all the state variants.
 | 
			
		||||
    fn states() -> Self::Iter;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// The label of a [`Schedule`](super::Schedule) that runs whenever [`State<S>`]
 | 
			
		||||
/// enters this state.
 | 
			
		||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
 | 
			
		||||
pub struct OnEnter<S: States>(pub S);
 | 
			
		||||
 | 
			
		||||
/// The label of a [`Schedule`](super::Schedule) that runs whenever [`State<S>`]
 | 
			
		||||
/// exits this state.
 | 
			
		||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
 | 
			
		||||
pub struct OnExit<S: States>(pub S);
 | 
			
		||||
 | 
			
		||||
/// A [`SystemSet`] that will run within \<insert-`bevy_core`-set-name\> 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<S: States>(pub S);
 | 
			
		||||
 | 
			
		||||
/// A finite-state machine whose transitions have associated schedules
 | 
			
		||||
/// ([`OnEnter(state)`] and [`OnExit(state)`]).
 | 
			
		||||
///
 | 
			
		||||
/// The current state value can be accessed through this resource. To *change* the state,
 | 
			
		||||
/// queue a transition in the [`NextState<S>`] resource, and it will be applied by the next
 | 
			
		||||
/// [`apply_state_transition::<S>`] system.
 | 
			
		||||
#[derive(Resource)]
 | 
			
		||||
pub struct State<S: States>(pub S);
 | 
			
		||||
 | 
			
		||||
/// The next state of [`State<S>`].
 | 
			
		||||
///
 | 
			
		||||
/// To queue a transition, just set the contained value to `Some(next_state)`.
 | 
			
		||||
#[derive(Resource)]
 | 
			
		||||
pub struct NextState<S: States>(pub Option<S>);
 | 
			
		||||
 | 
			
		||||
/// If a new state is queued in [`NextState<S>`], this system:
 | 
			
		||||
/// - Takes the new state value from [`NextState<S>`] and updates [`State<S>`].
 | 
			
		||||
/// - Runs the [`OnExit(exited_state)`] schedule.
 | 
			
		||||
/// - Runs the [`OnEnter(entered_state)`] schedule.
 | 
			
		||||
pub fn apply_state_transition<S: States>(world: &mut World) {
 | 
			
		||||
    if world.resource::<NextState<S>>().0.is_some() {
 | 
			
		||||
        let entered_state = world.resource_mut::<NextState<S>>().0.take().unwrap();
 | 
			
		||||
        let exited_state = mem::replace(
 | 
			
		||||
            &mut world.resource_mut::<State<S>>().0,
 | 
			
		||||
            entered_state.clone(),
 | 
			
		||||
        );
 | 
			
		||||
        world.run_schedule(OnExit(exited_state));
 | 
			
		||||
        world.run_schedule(OnEnter(entered_state));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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::<F>()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn component_access(&self) -> &Access<ComponentId> {
 | 
			
		||||
        self.system_meta.component_access_set.combined_access()
 | 
			
		||||
@ -149,9 +154,15 @@ where
 | 
			
		||||
            self.system_meta.name.as_ref(),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn default_labels(&self) -> Vec<SystemLabelId> {
 | 
			
		||||
        vec![self.func.as_system_label().as_label()]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn default_system_sets(&self) -> Vec<Box<dyn crate::schedule_v3::SystemSet>> {
 | 
			
		||||
        let set = crate::schedule_v3::SystemTypeSet::<F>::new();
 | 
			
		||||
        vec![Box::new(set)]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<In, Out, Param, Marker, T> AsSystemLabel<(In, Out, Param, Marker, IsExclusiveFunctionSystem)>
 | 
			
		||||
 | 
			
		||||
@ -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::<F>()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn component_access(&self) -> &Access<ComponentId> {
 | 
			
		||||
        self.system_meta.component_access_set.combined_access()
 | 
			
		||||
@ -453,9 +458,15 @@ where
 | 
			
		||||
            self.system_meta.name.as_ref(),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn default_labels(&self) -> Vec<SystemLabelId> {
 | 
			
		||||
        vec![self.func.as_system_label().as_label()]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn default_system_sets(&self) -> Vec<Box<dyn crate::schedule_v3::SystemSet>> {
 | 
			
		||||
        let set = crate::schedule_v3::SystemTypeSet::<F>::new();
 | 
			
		||||
        vec![Box::new(set)]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A [`SystemLabel`] that was automatically generated for a system on the basis of its `TypeId`.
 | 
			
		||||
 | 
			
		||||
@ -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<ComponentId>;
 | 
			
		||||
    /// Returns the system's archetype component [`Access`].
 | 
			
		||||
@ -64,6 +68,10 @@ pub trait System: Send + Sync + 'static {
 | 
			
		||||
    fn default_labels(&self) -> Vec<SystemLabelId> {
 | 
			
		||||
        Vec::new()
 | 
			
		||||
    }
 | 
			
		||||
    /// Returns the system's default [system sets](crate::schedule_v3::SystemSet).
 | 
			
		||||
    fn default_system_sets(&self) -> Vec<Box<dyn crate::schedule_v3::SystemSet>> {
 | 
			
		||||
        Vec::new()
 | 
			
		||||
    }
 | 
			
		||||
    /// Gets the system's last change tick
 | 
			
		||||
    fn get_last_change_tick(&self) -> u32;
 | 
			
		||||
    /// Sets the system's last change tick
 | 
			
		||||
 | 
			
		||||
@ -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<SystemA: System, SystemB: System<In = SystemA::Out>> System for PipeSystem<
 | 
			
		||||
        self.name.clone()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn type_id(&self) -> TypeId {
 | 
			
		||||
        TypeId::of::<(SystemA, SystemB)>()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
 | 
			
		||||
        &self.archetype_component_access
 | 
			
		||||
    }
 | 
			
		||||
@ -141,6 +145,18 @@ impl<SystemA: System, SystemB: System<In = SystemA::Out>> 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<crate::schedule::SystemLabelId> {
 | 
			
		||||
        let mut labels = self.system_a.default_labels();
 | 
			
		||||
        labels.extend(&self.system_b.default_labels());
 | 
			
		||||
        labels
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn default_system_sets(&self) -> Vec<Box<dyn crate::schedule_v3::SystemSet>> {
 | 
			
		||||
        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.
 | 
			
		||||
 | 
			
		||||
@ -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,7 @@ 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,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for World {
 | 
			
		||||
@ -81,6 +82,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,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1589,20 +1591,37 @@ 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: benchmark and optimize
 | 
			
		||||
    pub fn check_change_ticks(&mut self) {
 | 
			
		||||
        // Iterate over all component change ticks, clamping their age to max age
 | 
			
		||||
        // PERF: parallelize
 | 
			
		||||
        let change_tick = self.change_tick();
 | 
			
		||||
        if change_tick.wrapping_sub(self.last_check_tick) < CHECK_TICK_THRESHOLD {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let Storages {
 | 
			
		||||
            ref mut tables,
 | 
			
		||||
            ref mut sparse_sets,
 | 
			
		||||
            ref mut resources,
 | 
			
		||||
            ref mut non_send_resources,
 | 
			
		||||
        } = self.storages;
 | 
			
		||||
 | 
			
		||||
        #[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::<crate::schedule_v3::Schedules>() {
 | 
			
		||||
            schedules.check_change_ticks(change_tick);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.last_check_tick = change_tick;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn clear_entities(&mut self) {
 | 
			
		||||
 | 
			
		||||
@ -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_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 {
 | 
			
		||||
        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<dyn #trait_path> {
 | 
			
		||||
                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<dyn #trait_path> {
 | 
			
		||||
                std::boxed::Box::new(std::clone::Clone::clone(self))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
    .into()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Derive a label trait
 | 
			
		||||
///
 | 
			
		||||
/// # Args
 | 
			
		||||
 | 
			
		||||
@ -177,6 +177,10 @@ impl System for FixedTimestep {
 | 
			
		||||
        Cow::Borrowed(std::any::type_name::<FixedTimestep>())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn type_id(&self) -> std::any::TypeId {
 | 
			
		||||
        std::any::TypeId::of::<FixedTimestep>()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
 | 
			
		||||
        self.internal_system.archetype_component_access()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,8 @@ 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]
 | 
			
		||||
getrandom = {version = "0.2.0", features = ["js"]}
 | 
			
		||||
 | 
			
		||||
@ -60,6 +60,47 @@ where
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Macro to define a new label trait
 | 
			
		||||
///
 | 
			
		||||
/// # Example
 | 
			
		||||
///
 | 
			
		||||
/// ```
 | 
			
		||||
/// # use bevy_utils::define_boxed_label;
 | 
			
		||||
/// define_boxed_label!(MyNewLabelTrait);
 | 
			
		||||
/// ```
 | 
			
		||||
#[macro_export]
 | 
			
		||||
macro_rules! define_boxed_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<dyn $label_trait_name>;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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<H: ::std::hash::Hasher>(&self, state: &mut H) {
 | 
			
		||||
                self.dyn_hash(state);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        impl Clone for Box<dyn $label_trait_name> {
 | 
			
		||||
            fn clone(&self) -> Self {
 | 
			
		||||
                self.dyn_clone()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Macro to define a new label trait
 | 
			
		||||
///
 | 
			
		||||
/// # Example
 | 
			
		||||
 | 
			
		||||
@ -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,8 @@ 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;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										122
									
								
								crates/bevy_utils/src/syncunsafecell.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								crates/bevy_utils/src/syncunsafecell.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,122 @@
 | 
			
		||||
//! 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 native 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<T: ?Sized> {
 | 
			
		||||
    value: UnsafeCell<T>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SAFETY: `T` is Sync, caller is responsible for upholding rust safety rules
 | 
			
		||||
unsafe impl<T: ?Sized + Sync> Sync for SyncUnsafeCell<T> {}
 | 
			
		||||
 | 
			
		||||
impl<T> SyncUnsafeCell<T> {
 | 
			
		||||
    /// 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<T: ?Sized> SyncUnsafeCell<T> {
 | 
			
		||||
    /// 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<T>` 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<T>` from a `&mut T`.
 | 
			
		||||
    pub fn from_mut(t: &mut T) -> &SyncUnsafeCell<T> {
 | 
			
		||||
        // SAFETY: `&mut` ensures unique access, and `UnsafeCell<T>` and `SyncUnsafeCell<T>`
 | 
			
		||||
        // have #[repr(transparent)]
 | 
			
		||||
        unsafe { &*(t as *mut T as *const SyncUnsafeCell<T>) }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T> SyncUnsafeCell<[T]> {
 | 
			
		||||
    /// Returns a `&[SyncUnsafeCell<T>]` 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<i32>] = cell_slice.as_slice_of_cells();
 | 
			
		||||
    ///
 | 
			
		||||
    /// assert_eq!(slice_cell.len(), 3);
 | 
			
		||||
    /// ```
 | 
			
		||||
    pub fn as_slice_of_cells(&self) -> &[SyncUnsafeCell<T>] {
 | 
			
		||||
        // SAFETY: `UnsafeCell<T>` and `SyncUnsafeCell<T>` have #[repr(transparent)]
 | 
			
		||||
        // therefore:
 | 
			
		||||
        // - `SyncUnsafeCell<T>` has the same layout as `T`
 | 
			
		||||
        // - `SyncUnsafeCell<[T]>` has the same layout as `[T]`
 | 
			
		||||
        // - `SyncUnsafeCell<[T]>` has the same layout as `[SyncUnsafeCell<T>]`
 | 
			
		||||
        unsafe { &*(self as *const SyncUnsafeCell<[T]> as *const [SyncUnsafeCell<T>]) }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T: Default> Default for SyncUnsafeCell<T> {
 | 
			
		||||
    /// Creates an `SyncUnsafeCell`, with the `Default` value for T.
 | 
			
		||||
    fn default() -> SyncUnsafeCell<T> {
 | 
			
		||||
        SyncUnsafeCell::new(Default::default())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T> From<T> for SyncUnsafeCell<T> {
 | 
			
		||||
    /// Creates a new `SyncUnsafeCell<T>` containing the given value.
 | 
			
		||||
    fn from(t: T) -> SyncUnsafeCell<T> {
 | 
			
		||||
        SyncUnsafeCell::new(t)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user