Schedule-First: the new and improved add_systems (#8079)

Co-authored-by: Mike <mike.hsu@gmail.com>
This commit is contained in:
Carter Anderson 2023-03-17 18:45:34 -07:00 committed by GitHub
parent bca4b36d4d
commit aefe1f0739
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
258 changed files with 2177 additions and 2798 deletions

View file

@ -9,7 +9,7 @@ fn setup(system_count: usize) -> (World, Schedule) {
fn empty() {}
let mut schedule = Schedule::new();
for _ in 0..system_count {
schedule.add_system(empty);
schedule.add_systems(empty);
}
schedule.run(&mut world);
(world, schedule)

View file

@ -154,7 +154,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("empty_archetypes");
for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] {
let (mut world, mut schedule) = setup(true, |schedule| {
schedule.add_system(iter);
schedule.add_systems(iter);
});
add_archetypes(&mut world, archetype_count);
world.clear_entities();
@ -185,7 +185,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
}
for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] {
let (mut world, mut schedule) = setup(true, |schedule| {
schedule.add_system(for_each);
schedule.add_systems(for_each);
});
add_archetypes(&mut world, archetype_count);
world.clear_entities();
@ -216,7 +216,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
}
for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] {
let (mut world, mut schedule) = setup(true, |schedule| {
schedule.add_system(par_for_each);
schedule.add_systems(par_for_each);
});
add_archetypes(&mut world, archetype_count);
world.clear_entities();

View file

@ -19,7 +19,7 @@ pub fn run_condition_yes(criterion: &mut Criterion) {
fn empty() {}
for amount in 0..21 {
let mut schedule = Schedule::new();
schedule.add_system(empty.run_if(yes));
schedule.add_systems(empty.run_if(yes));
for _ in 0..amount {
schedule.add_systems((empty, empty, empty, empty, empty).distributive_run_if(yes));
}
@ -42,7 +42,7 @@ pub fn run_condition_no(criterion: &mut Criterion) {
fn empty() {}
for amount in 0..21 {
let mut schedule = Schedule::new();
schedule.add_system(empty.run_if(no));
schedule.add_systems(empty.run_if(no));
for _ in 0..amount {
schedule.add_systems((empty, empty, empty, empty, empty).distributive_run_if(no));
}
@ -72,7 +72,7 @@ pub fn run_condition_yes_with_query(criterion: &mut Criterion) {
}
for amount in 0..21 {
let mut schedule = Schedule::new();
schedule.add_system(empty.run_if(yes_with_query));
schedule.add_systems(empty.run_if(yes_with_query));
for _ in 0..amount {
schedule.add_systems(
(empty, empty, empty, empty, empty).distributive_run_if(yes_with_query),
@ -101,7 +101,7 @@ pub fn run_condition_yes_with_resource(criterion: &mut Criterion) {
}
for amount in 0..21 {
let mut schedule = Schedule::new();
schedule.add_system(empty.run_if(yes_with_resource));
schedule.add_systems(empty.run_if(yes_with_resource));
for _ in 0..amount {
schedule.add_systems(
(empty, empty, empty, empty, empty).distributive_run_if(yes_with_resource),

View file

@ -23,7 +23,7 @@ pub fn empty_systems(criterion: &mut Criterion) {
for amount in 0..5 {
let mut schedule = Schedule::new();
for _ in 0..amount {
schedule.add_system(empty);
schedule.add_systems(empty);
}
schedule.run(&mut world);
group.bench_function(&format!("{:03}_systems", amount), |bencher| {

View file

@ -1,4 +1,4 @@
use bevy_app::App;
use bevy_app::{App, Update};
use bevy_ecs::prelude::*;
use criterion::Criterion;
@ -72,7 +72,7 @@ pub fn build_schedule(criterion: &mut Criterion) {
group.measurement_time(std::time::Duration::from_secs(15));
// Method: generate a set of `graph_size` systems which have a One True Ordering.
// Add system to the schedule with full constraints. Hopefully this should be maximimally
// Add system to the schedule with full constraints. Hopefully this should be maximally
// difficult for bevy to figure out.
let labels: Vec<_> = (0..1000).map(|i| NumSet(i)).collect();
@ -83,7 +83,7 @@ pub fn build_schedule(criterion: &mut Criterion) {
bencher.iter(|| {
let mut app = App::new();
for _ in 0..graph_size {
app.add_system(empty_system);
app.add_systems(Update, empty_system);
}
app.update();
});
@ -93,7 +93,7 @@ pub fn build_schedule(criterion: &mut Criterion) {
group.bench_function(format!("{graph_size}_schedule"), |bencher| {
bencher.iter(|| {
let mut app = App::new();
app.add_system(empty_system.in_set(DummySet));
app.add_systems(Update, empty_system.in_set(DummySet));
// Build a fully-connected dependency graph describing the One True Ordering.
// Not particularly realistic but this can be refined later.
@ -105,7 +105,7 @@ pub fn build_schedule(criterion: &mut Criterion) {
for label in &labels[i + 1..graph_size] {
sys = sys.before(*label);
}
app.add_system(sys);
app.add_systems(Update, sys);
}
// Run the app for a single frame.
// This is necessary since dependency resolution does not occur until the game runs.

View file

@ -5,7 +5,7 @@
use std::ops::Deref;
use std::time::Duration;
use bevy_app::{App, CoreSet, Plugin};
use bevy_app::{App, Plugin, PostUpdate};
use bevy_asset::{AddAsset, Assets, Handle};
use bevy_core::Name;
use bevy_ecs::prelude::*;
@ -550,10 +550,9 @@ impl Plugin for AnimationPlugin {
app.add_asset::<AnimationClip>()
.register_asset_reflect::<AnimationClip>()
.register_type::<AnimationPlayer>()
.add_system(
animation_player
.in_base_set(CoreSet::PostUpdate)
.before(TransformSystem::TransformPropagate),
.add_systems(
PostUpdate,
animation_player.before(TransformSystem::TransformPropagate),
);
}
}

View file

@ -1,13 +1,12 @@
use crate::{
CoreSchedule, CoreSet, IntoSystemAppConfig, IntoSystemAppConfigs, Plugin, PluginGroup,
StartupSet, SystemAppConfig,
First, Main, MainSchedulePlugin, Plugin, PluginGroup, Startup, StateTransition, Update,
};
pub use bevy_derive::AppLabel;
use bevy_ecs::{
prelude::*,
schedule::{
apply_state_transition, common_conditions::run_once as run_once_condition,
run_enter_schedule, BoxedScheduleLabel, IntoSystemConfig, IntoSystemSetConfigs,
run_enter_schedule, BoxedScheduleLabel, IntoSystemConfigs, IntoSystemSetConfigs,
ScheduleLabel,
},
};
@ -53,7 +52,7 @@ pub(crate) enum AppError {
/// #
/// fn main() {
/// App::new()
/// .add_system(hello_world_system)
/// .add_systems(Update, hello_world_system)
/// .run();
/// }
///
@ -74,12 +73,10 @@ pub struct App {
pub runner: Box<dyn Fn(App) + Send>, // Send bound is required to make App Send
/// The schedule that systems are added to by default.
///
/// This is initially set to [`CoreSchedule::Main`].
pub default_schedule_label: BoxedScheduleLabel,
/// The schedule that controls the outer loop of schedule execution.
/// The schedule that runs the main loop of schedule execution.
///
/// This is initially set to [`CoreSchedule::Outer`].
pub outer_schedule_label: BoxedScheduleLabel,
/// This is initially set to [`Main`].
pub main_schedule_label: BoxedScheduleLabel,
sub_apps: HashMap<AppLabelId, SubApp>,
plugin_registry: Vec<Box<dyn Plugin>>,
plugin_name_added: HashSet<String>,
@ -104,7 +101,7 @@ impl Debug for App {
/// # Example
///
/// ```rust
/// # use bevy_app::{App, AppLabel, SubApp, CoreSchedule};
/// # use bevy_app::{App, AppLabel, SubApp, Main};
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs::schedule::ScheduleLabel;
///
@ -122,12 +119,10 @@ impl Debug for App {
/// // create a app with a resource and a single schedule
/// let mut sub_app = App::empty();
/// // add an outer schedule that runs the main schedule
/// sub_app.add_simple_outer_schedule();
/// sub_app.insert_resource(Val(100));
///
/// // initialize main schedule
/// sub_app.init_schedule(CoreSchedule::Main);
/// sub_app.add_system(|counter: Res<Val>| {
/// sub_app.add_systems(Main, |counter: Res<Val>| {
/// // since we assigned the value from the main world in extract
/// // we see that value instead of 100
/// assert_eq!(counter.0, 10);
@ -169,7 +164,7 @@ impl SubApp {
pub fn run(&mut self) {
self.app
.world
.run_schedule_ref(&*self.app.outer_schedule_label);
.run_schedule_ref(&*self.app.main_schedule_label);
self.app.world.clear_trackers();
}
@ -195,8 +190,7 @@ impl Default for App {
#[cfg(feature = "bevy_reflect")]
app.init_resource::<AppTypeRegistry>();
app.add_default_schedules();
app.add_plugin(MainSchedulePlugin);
app.add_event::<AppExit>();
#[cfg(feature = "bevy_ci_testing")]
@ -211,8 +205,6 @@ impl Default for App {
impl App {
/// Creates a new [`App`] with some default structure to enable core engine features.
/// This is the preferred constructor for most use cases.
///
/// This calls [`App::add_default_schedules`].
pub fn new() -> App {
App::default()
}
@ -229,8 +221,7 @@ impl App {
sub_apps: HashMap::default(),
plugin_registry: Vec::default(),
plugin_name_added: Default::default(),
default_schedule_label: Box::new(CoreSchedule::Main),
outer_schedule_label: Box::new(CoreSchedule::Outer),
main_schedule_label: Box::new(Main),
building_plugin_depth: 0,
}
}
@ -240,9 +231,8 @@ impl App {
/// This method also updates sub apps.
/// See [`insert_sub_app`](Self::insert_sub_app) for more details.
///
/// The schedule run by this method is determined by the [`outer_schedule_label`](App) field.
/// In normal usage, this is [`CoreSchedule::Outer`], which will run [`CoreSchedule::Startup`]
/// the first time the app is run, then [`CoreSchedule::Main`] on every call of this method.
/// The schedule run by this method is determined by the [`main_schedule_label`](App) field.
/// By default this is [`Main`].
///
/// # Panics
///
@ -251,7 +241,7 @@ impl App {
{
#[cfg(feature = "trace")]
let _bevy_frame_update_span = info_span!("main app").entered();
self.world.run_schedule_ref(&*self.outer_schedule_label);
self.world.run_schedule_ref(&*self.main_schedule_label);
}
for (_label, sub_app) in self.sub_apps.iter_mut() {
#[cfg(feature = "trace")]
@ -317,13 +307,13 @@ impl App {
/// Adds [`State<S>`] and [`NextState<S>`] resources, [`OnEnter`] and [`OnExit`] schedules
/// for each state variant (if they don't already exist), an instance of [`apply_state_transition::<S>`] in
/// [`CoreSet::StateTransitions`] so that transitions happen before [`CoreSet::Update`] and
/// a instance of [`run_enter_schedule::<S>`] in [`CoreSet::StateTransitions`] with a
/// [`StateTransition`] so that transitions happen before [`Update`] and
/// a instance of [`run_enter_schedule::<S>`] in [`StateTransition`] with a
/// [`run_once`](`run_once_condition`) condition to run the on enter schedule of the
/// initial state.
///
/// This also adds an [`OnUpdate`] system set for each state variant,
/// which runs during [`CoreSet::Update`] after the transitions are applied.
/// which runs during [`Update`] after the transitions are applied.
/// These system sets only run if the [`State<S>`] resource matches the respective state variant.
///
/// If you would like to control how other systems run based on the current state,
@ -332,31 +322,19 @@ impl App {
/// Note that you can also apply state transitions at other points in the schedule
/// by adding the [`apply_state_transition`] system manually.
pub fn add_state<S: States>(&mut self) -> &mut Self {
self.init_resource::<State<S>>();
self.init_resource::<NextState<S>>();
let mut schedules = self.world.resource_mut::<Schedules>();
let Some(default_schedule) = schedules.get_mut(&*self.default_schedule_label) else {
let schedule_label = &self.default_schedule_label;
panic!("Default schedule {schedule_label:?} does not exist.")
};
default_schedule.add_systems(
(
run_enter_schedule::<S>.run_if(run_once_condition()),
apply_state_transition::<S>,
)
.chain()
.in_base_set(CoreSet::StateTransitions),
);
self.init_resource::<State<S>>()
.init_resource::<NextState<S>>()
.add_systems(
StateTransition,
(
run_enter_schedule::<S>.run_if(run_once_condition()),
apply_state_transition::<S>,
)
.chain(),
);
for variant in S::variants() {
default_schedule.configure_set(
OnUpdate(variant.clone())
.in_base_set(CoreSet::Update)
.run_if(in_state(variant)),
);
self.configure_set(Update, OnUpdate(variant.clone()).run_if(in_state(variant)));
}
// The OnEnter, OnExit, and OnTransition schedules are lazily initialized
@ -382,30 +360,15 @@ impl App {
/// #
/// app.add_system(my_system);
/// ```
pub fn add_system<M>(&mut self, system: impl IntoSystemAppConfig<M>) -> &mut Self {
let mut schedules = self.world.resource_mut::<Schedules>();
let SystemAppConfig { system, schedule } = system.into_app_config();
if let Some(schedule_label) = schedule {
if let Some(schedule) = schedules.get_mut(&*schedule_label) {
schedule.add_system(system);
} else {
let mut schedule = Schedule::new();
schedule.add_system(system);
schedules.insert(schedule_label, schedule);
}
} else if let Some(default_schedule) = schedules.get_mut(&*self.default_schedule_label) {
default_schedule.add_system(system);
} else {
let schedule_label = &self.default_schedule_label;
panic!("Default schedule {schedule_label:?} does not exist.")
}
self
#[deprecated(
since = "0.11.0",
note = "Please use `add_systems` instead. If you didn't change the default base set, you should use `add_systems(Update, your_system).`"
)]
pub fn add_system<M>(&mut self, system: impl IntoSystemConfigs<M>) -> &mut Self {
self.add_systems(Update, system)
}
/// Adds a system to the default system set and schedule of the app's [`Schedules`].
/// Adds a system to the given schedule in this app's [`Schedules`].
///
/// # Examples
///
@ -418,36 +381,27 @@ impl App {
/// # fn system_b() {}
/// # fn system_c() {}
/// #
/// app.add_systems((system_a, system_b, system_c));
/// app.add_systems(Update, (system_a, system_b, system_c));
/// ```
pub fn add_systems<M>(&mut self, systems: impl IntoSystemAppConfigs<M>) -> &mut Self {
pub fn add_systems<M>(
&mut self,
schedule: impl ScheduleLabel,
systems: impl IntoSystemConfigs<M>,
) -> &mut Self {
let mut schedules = self.world.resource_mut::<Schedules>();
match systems.into_app_configs().0 {
crate::InnerConfigs::Blanket { systems, schedule } => {
let schedule = if let Some(label) = schedule {
schedules
.get_mut(&*label)
.unwrap_or_else(|| panic!("Schedule '{label:?}' does not exist."))
} else {
let label = &*self.default_schedule_label;
schedules
.get_mut(label)
.unwrap_or_else(|| panic!("Default schedule '{label:?}' does not exist."))
};
schedule.add_systems(systems);
}
crate::InnerConfigs::Granular(systems) => {
for system in systems {
self.add_system(system);
}
}
if let Some(schedule) = schedules.get_mut(&schedule) {
schedule.add_systems(systems);
} else {
let mut new_schedule = Schedule::new();
new_schedule.add_systems(systems);
schedules.insert(schedule, new_schedule);
}
self
}
/// Adds a system to [`CoreSchedule::Startup`].
/// Adds a system to [`Startup`].
///
/// These systems will run exactly once, at the start of the [`App`]'s lifecycle.
/// To add a system that runs every frame, see [`add_system`](Self::add_system).
@ -463,13 +417,17 @@ impl App {
/// }
///
/// App::new()
/// .add_startup_system(my_startup_system);
/// .add_systems(Startup, my_startup_system);
/// ```
pub fn add_startup_system<M>(&mut self, system: impl IntoSystemConfig<M>) -> &mut Self {
self.add_system(system.in_schedule(CoreSchedule::Startup))
#[deprecated(
since = "0.11.0",
note = "Please use `add_systems` instead. If you didn't change the default base set, you should use `add_systems(Startup, your_system).`"
)]
pub fn add_startup_system<M>(&mut self, system: impl IntoSystemConfigs<M>) -> &mut Self {
self.add_systems(Startup, system)
}
/// Adds a collection of systems to [`CoreSchedule::Startup`].
/// Adds a collection of systems to [`Startup`].
///
/// # Examples
///
@ -482,88 +440,58 @@ impl App {
/// # fn startup_system_b() {}
/// # fn startup_system_c() {}
/// #
/// app.add_startup_systems((
/// app.add_systems(Startup, (
/// startup_system_a,
/// startup_system_b,
/// startup_system_c,
/// ));
/// ```
#[deprecated(
since = "0.11.0",
note = "Please use `add_systems` instead. If you didn't change the default base set, you should use `add_systems(Startup, your_system).`"
)]
pub fn add_startup_systems<M>(&mut self, systems: impl IntoSystemConfigs<M>) -> &mut Self {
self.add_systems(systems.into_configs().in_schedule(CoreSchedule::Startup))
self.add_systems(Startup, systems.into_configs())
}
/// Configures a system set in the default schedule, adding the set if it does not exist.
pub fn configure_set(&mut self, set: impl IntoSystemSetConfig) -> &mut Self {
self.world
.resource_mut::<Schedules>()
.get_mut(&*self.default_schedule_label)
.unwrap()
.configure_set(set);
pub fn configure_set(
&mut self,
schedule: impl ScheduleLabel,
set: impl IntoSystemSetConfig,
) -> &mut Self {
let mut schedules = self.world.resource_mut::<Schedules>();
if let Some(schedule) = schedules.get_mut(&schedule) {
schedule.configure_set(set);
} else {
let mut new_schedule = Schedule::new();
new_schedule.configure_set(set);
schedules.insert(schedule, new_schedule);
}
self
}
/// Configures a collection of system sets in the default schedule, adding any sets that do not exist.
pub fn configure_sets(&mut self, sets: impl IntoSystemSetConfigs) -> &mut Self {
self.world
.resource_mut::<Schedules>()
.get_mut(&*self.default_schedule_label)
.unwrap()
.configure_sets(sets);
self
}
/// Adds standardized schedules and labels to an [`App`].
///
/// Adding these schedules is necessary to make almost all core engine features work.
/// This is typically done implicitly by calling `App::default`, which is in turn called by
/// [`App::new`].
///
/// The schedules added are defined in the [`CoreSchedule`] enum,
/// and have a starting configuration defined by:
///
/// - [`CoreSchedule::Outer`]: uses [`CoreSchedule::outer_schedule`]
/// - [`CoreSchedule::Startup`]: uses [`StartupSet::base_schedule`]
/// - [`CoreSchedule::Main`]: uses [`CoreSet::base_schedule`]
/// - [`CoreSchedule::FixedUpdate`]: no starting configuration
///
/// # Examples
///
/// ```
/// use bevy_app::App;
/// use bevy_ecs::schedule::Schedules;
///
/// let app = App::empty()
/// .init_resource::<Schedules>()
/// .add_default_schedules()
/// .update();
/// ```
pub fn add_default_schedules(&mut self) -> &mut Self {
self.add_schedule(CoreSchedule::Outer, CoreSchedule::outer_schedule());
self.add_schedule(CoreSchedule::Startup, StartupSet::base_schedule());
self.add_schedule(CoreSchedule::Main, CoreSet::base_schedule());
self.init_schedule(CoreSchedule::FixedUpdate);
self
}
/// adds a single threaded outer schedule to the [`App`] that just runs the main schedule
pub fn add_simple_outer_schedule(&mut self) -> &mut Self {
fn run_main_schedule(world: &mut World) {
world.run_schedule(CoreSchedule::Main);
pub fn configure_sets(
&mut self,
schedule: impl ScheduleLabel,
sets: impl IntoSystemSetConfigs,
) -> &mut Self {
let mut schedules = self.world.resource_mut::<Schedules>();
if let Some(schedule) = schedules.get_mut(&schedule) {
schedule.configure_sets(sets);
} else {
let mut new_schedule = Schedule::new();
new_schedule.configure_sets(sets);
schedules.insert(schedule, new_schedule);
}
self.edit_schedule(CoreSchedule::Outer, |schedule| {
schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded);
schedule.add_system(run_main_schedule);
});
self
}
/// Setup the application to manage events of type `T`.
///
/// This is done by adding a [`Resource`] of type [`Events::<T>`],
/// and inserting an [`update_system`](Events::update_system) into [`CoreSet::First`].
/// and inserting an [`update_system`](Events::update_system) into [`First`].
///
/// See [`Events`] for defining events.
///
@ -584,7 +512,7 @@ impl App {
{
if !self.world.contains_resource::<Events<T>>() {
self.init_resource::<Events<T>>()
.add_system(Events::<T>::update_system.in_base_set(CoreSet::First));
.add_systems(First, Events::<T>::update_system);
}
self
}
@ -1025,7 +953,7 @@ mod tests {
system::Commands,
};
use crate::{App, IntoSystemAppConfig, IntoSystemAppConfigs, Plugin};
use crate::{App, Plugin};
struct PluginA;
impl Plugin for PluginA {
@ -1097,31 +1025,11 @@ mod tests {
commands.spawn_empty();
}
#[test]
fn add_system_should_create_schedule_if_it_does_not_exist() {
let mut app = App::new();
app.add_system(foo.in_schedule(OnEnter(AppState::MainMenu)))
.add_state::<AppState>();
app.world.run_schedule(OnEnter(AppState::MainMenu));
assert_eq!(app.world.entities().len(), 1);
}
#[test]
fn add_system_should_create_schedule_if_it_does_not_exist2() {
let mut app = App::new();
app.add_state::<AppState>()
.add_system(foo.in_schedule(OnEnter(AppState::MainMenu)));
app.world.run_schedule(OnEnter(AppState::MainMenu));
assert_eq!(app.world.entities().len(), 1);
}
#[test]
fn add_systems_should_create_schedule_if_it_does_not_exist() {
let mut app = App::new();
app.add_state::<AppState>()
.add_systems((foo, bar).in_schedule(OnEnter(AppState::MainMenu)));
.add_systems(OnEnter(AppState::MainMenu), (foo, bar));
app.world.run_schedule(OnEnter(AppState::MainMenu));
assert_eq!(app.world.entities().len(), 2);
@ -1130,7 +1038,7 @@ mod tests {
#[test]
fn add_systems_should_create_schedule_if_it_does_not_exist2() {
let mut app = App::new();
app.add_systems((foo, bar).in_schedule(OnEnter(AppState::MainMenu)))
app.add_systems(OnEnter(AppState::MainMenu), (foo, bar))
.add_state::<AppState>();
app.world.run_schedule(OnEnter(AppState::MainMenu));

View file

@ -1,4 +1,4 @@
use crate::{app::AppExit, App};
use crate::{app::AppExit, App, Update};
use serde::Deserialize;
use bevy_ecs::prelude::Resource;
@ -47,7 +47,7 @@ pub(crate) fn setup_app(app: &mut App) -> &mut App {
};
app.insert_resource(config)
.add_system(ci_testing_exit_after);
.add_systems(Update, ci_testing_exit_after);
app
}

View file

@ -1,294 +0,0 @@
use bevy_ecs::{
all_tuples,
schedule::{
BaseSystemSet, BoxedScheduleLabel, Condition, FreeSystemSet, IntoSystemConfig,
IntoSystemSet, ScheduleLabel, SystemConfig, SystemConfigs,
},
};
use crate::CoreSchedule;
/// A [`System`] with [`App`]-aware scheduling metadata.
///
/// [`System`]: bevy_ecs::prelude::System
/// [`App`]: crate::App
pub struct SystemAppConfig {
pub(crate) system: SystemConfig,
pub(crate) schedule: Option<BoxedScheduleLabel>,
}
/// Types that can be converted into a [`SystemAppConfig`].
///
/// This has been implemented for all `System<In = (), Out = ()>` trait objects
/// and all functions that convert into such.
pub trait IntoSystemAppConfig<Marker>: Sized {
/// Converts into a [`SystemAppConfig`].
fn into_app_config(self) -> SystemAppConfig;
/// Adds the system to the provided `schedule`.
///
/// If a schedule is not specified, it will be added to the [`App`]'s default schedule.
///
/// [`App`]: crate::App
///
/// # Panics
///
/// If the system has already been assigned to a schedule.
#[track_caller]
fn in_schedule(self, schedule: impl ScheduleLabel) -> SystemAppConfig {
let mut config = self.into_app_config();
if let Some(old_schedule) = &config.schedule {
panic!(
"Cannot add system to schedule '{schedule:?}': it is already in '{old_schedule:?}'."
);
}
config.schedule = Some(Box::new(schedule));
config
}
/// Adds the system to [`CoreSchedule::Startup`].
/// This is a shorthand for `self.in_schedule(CoreSchedule::Startup)`.
///
/// Systems in this schedule will run exactly once, at the start of the [`App`]'s lifecycle.
///
/// [`App`]: crate::App
///
/// # Examples
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// #
/// fn my_startup_system(_commands: Commands) {
/// println!("My startup system");
/// }
///
/// App::new()
/// .add_system(my_startup_system.on_startup())
/// .run();
/// ```
///
/// # Panics
///
/// If the system has already been assigned to a schedule.
#[inline]
fn on_startup(self) -> SystemAppConfig {
self.in_schedule(CoreSchedule::Startup)
}
}
impl IntoSystemConfig<(), Self> for SystemAppConfig {
fn into_config(self) -> Self {
self
}
#[track_caller]
fn in_set(self, set: impl FreeSystemSet) -> Self {
let Self { system, schedule } = self;
Self {
system: system.in_set(set),
schedule,
}
}
#[track_caller]
fn in_base_set(self, set: impl BaseSystemSet) -> Self {
let Self { system, schedule } = self;
Self {
system: system.in_base_set(set),
schedule,
}
}
fn no_default_base_set(self) -> Self {
let Self { system, schedule } = self;
Self {
system: system.no_default_base_set(),
schedule,
}
}
fn before<M>(self, set: impl IntoSystemSet<M>) -> Self {
let Self { system, schedule } = self;
Self {
system: system.before(set),
schedule,
}
}
fn after<M>(self, set: impl IntoSystemSet<M>) -> Self {
let Self { system, schedule } = self;
Self {
system: system.after(set),
schedule,
}
}
fn run_if<P>(self, condition: impl Condition<P>) -> Self {
let Self { system, schedule } = self;
Self {
system: system.run_if(condition),
schedule,
}
}
fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> Self {
let Self { system, schedule } = self;
Self {
system: system.ambiguous_with(set),
schedule,
}
}
fn ambiguous_with_all(self) -> Self {
let Self { system, schedule } = self;
Self {
system: system.ambiguous_with_all(),
schedule,
}
}
}
impl IntoSystemAppConfig<()> for SystemAppConfig {
fn into_app_config(self) -> SystemAppConfig {
self
}
}
impl<Marker, T> IntoSystemAppConfig<Marker> for T
where
T: IntoSystemConfig<Marker>,
{
fn into_app_config(self) -> SystemAppConfig {
SystemAppConfig {
system: self.into_config(),
schedule: None,
}
}
}
/// A collection of [`SystemAppConfig`]s.
pub struct SystemAppConfigs(pub(crate) InnerConfigs);
pub(crate) enum InnerConfigs {
/// This came from an instance of `SystemConfigs`.
/// All systems are in the same schedule.
Blanket {
systems: SystemConfigs,
schedule: Option<BoxedScheduleLabel>,
},
/// This came from several separate instances of `SystemAppConfig`.
/// Each system gets its own schedule.
Granular(Vec<SystemAppConfig>),
}
/// Types that can convert into [`SystemAppConfigs`].
pub trait IntoSystemAppConfigs<Marker>: Sized {
/// Converts to [`SystemAppConfigs`].
fn into_app_configs(self) -> SystemAppConfigs;
/// Adds the systems to the provided `schedule`.
///
/// If a schedule with specified label does not exist, it will be created.
///
/// If a schedule with the specified label does not exist, an empty one will be created.
///
///
/// [`App`]: crate::App
///
/// # Panics
///
/// If any of the systems have already been assigned to a schedule.
#[track_caller]
fn in_schedule(self, label: impl ScheduleLabel) -> SystemAppConfigs {
let mut configs = self.into_app_configs();
match &mut configs.0 {
InnerConfigs::Blanket { schedule, .. } => {
if schedule.is_some() {
panic!(
"Cannot add systems to the schedule '{label:?}: they are already in '{schedule:?}'"
);
}
*schedule = Some(Box::new(label));
}
InnerConfigs::Granular(configs) => {
for SystemAppConfig { schedule, .. } in configs {
if schedule.is_some() {
panic!(
"Cannot add system to the schedule '{label:?}': it is already in '{schedule:?}'."
);
}
*schedule = Some(label.dyn_clone());
}
}
}
configs
}
/// Adds the systems to [`CoreSchedule::Startup`].
/// This is a shorthand for `self.in_schedule(CoreSchedule::Startup)`.
///
/// # Examples
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// #
/// # let mut app = App::new();
/// # fn startup_system_a() {}
/// # fn startup_system_b() {}
/// # fn startup_system_c() {}
/// #
/// app.add_systems(
/// (
/// startup_system_a,
/// startup_system_b,
/// startup_system_c,
/// )
/// .on_startup()
/// );
/// ```
///
/// # Panics
///
/// If any of the systems have already been assigned to a schedule.
#[track_caller]
fn on_startup(self) -> SystemAppConfigs {
self.in_schedule(CoreSchedule::Startup)
}
}
impl IntoSystemAppConfigs<()> for SystemAppConfigs {
fn into_app_configs(self) -> SystemAppConfigs {
self
}
}
impl IntoSystemAppConfigs<()> for SystemConfigs {
fn into_app_configs(self) -> SystemAppConfigs {
SystemAppConfigs(InnerConfigs::Blanket {
systems: self,
schedule: None,
})
}
}
macro_rules! impl_system_collection {
($(($param: ident, $sys: ident)),*) => {
impl<$($param, $sys),*> IntoSystemAppConfigs<($($param,)*)> for ($($sys,)*)
where
$($sys: IntoSystemAppConfig<$param>),*
{
#[allow(non_snake_case)]
fn into_app_configs(self) -> SystemAppConfigs {
let ($($sys,)*) = self;
SystemAppConfigs(InnerConfigs::Granular(vec![$($sys.into_app_config(),)*]))
}
}
}
}
all_tuples!(impl_system_collection, 0, 15, P, S);

View file

@ -3,7 +3,7 @@
#![warn(missing_docs)]
mod app;
mod config;
mod main_schedule;
mod plugin;
mod plugin_group;
mod schedule_runner;
@ -13,7 +13,7 @@ mod ci_testing;
pub use app::*;
pub use bevy_derive::DynamicPlugin;
pub use config::*;
pub use main_schedule::*;
pub use plugin::*;
pub use plugin_group::*;
pub use schedule_runner::*;
@ -26,197 +26,10 @@ pub mod prelude {
#[doc(hidden)]
pub use crate::{
app::App,
config::{IntoSystemAppConfig, IntoSystemAppConfigs},
CoreSchedule, CoreSet, DynamicPlugin, Plugin, PluginGroup, StartupSet,
main_schedule::{
First, FixedUpdate, Last, Main, PostStartup, PostUpdate, PreStartup, PreUpdate,
Startup, StateTransition, Update,
},
DynamicPlugin, Plugin, PluginGroup,
};
}
use bevy_ecs::{
schedule::{
apply_system_buffers, IntoSystemConfig, IntoSystemSetConfigs, Schedule, ScheduleLabel,
SystemSet,
},
system::Local,
world::World,
};
/// The names of the default [`App`] schedules.
///
/// The corresponding [`Schedule`](bevy_ecs::schedule::Schedule) objects are added by [`App::add_default_schedules`].
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub enum CoreSchedule {
/// The schedule that runs once when the app starts.
Startup,
/// The schedule that contains the app logic that is evaluated each tick of [`App::update()`].
Main,
/// The schedule that controls which schedules run.
///
/// This is typically created using the [`CoreSchedule::outer_schedule`] method,
/// and does not need to manipulated during ordinary use.
Outer,
/// The schedule that contains systems which only run after a fixed period of time has elapsed.
///
/// The exclusive `run_fixed_update_schedule` system runs this schedule during the [`CoreSet::FixedUpdate`] system set.
FixedUpdate,
}
impl CoreSchedule {
/// An exclusive system that controls which schedule should be running.
///
/// [`CoreSchedule::Main`] is always run.
///
/// If this is the first time this system has been run, [`CoreSchedule::Startup`] will run before [`CoreSchedule::Main`].
pub fn outer_loop(world: &mut World, mut run_at_least_once: Local<bool>) {
if !*run_at_least_once {
world.run_schedule(CoreSchedule::Startup);
*run_at_least_once = true;
}
world.run_schedule(CoreSchedule::Main);
}
/// Initializes a single threaded schedule for [`CoreSchedule::Outer`] that contains the [`outer_loop`](CoreSchedule::outer_loop) system.
pub fn outer_schedule() -> Schedule {
let mut schedule = Schedule::new();
schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded);
schedule.add_system(Self::outer_loop);
schedule
}
}
/// The names of the default [`App`] system sets.
///
/// These are ordered in the same order they are listed.
///
/// The corresponding [`SystemSets`](bevy_ecs::schedule::SystemSet) are added by [`App::add_default_schedules`].
///
/// The `*Flush` sets are assigned to the copy of [`apply_system_buffers`]
/// that runs immediately after the matching system set.
/// These can be useful for ordering, but you almost never want to add your systems to these sets.
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
#[system_set(base)]
pub enum CoreSet {
/// Runs before all other members of this set.
First,
/// The copy of [`apply_system_buffers`] that runs immediately after `First`.
FirstFlush,
/// Runs before [`CoreSet::Update`].
PreUpdate,
/// The copy of [`apply_system_buffers`] that runs immediately after `PreUpdate`.
PreUpdateFlush,
/// Applies [`State`](bevy_ecs::schedule::State) transitions
StateTransitions,
/// Runs systems that should only occur after a fixed period of time.
///
/// The `run_fixed_update_schedule` system runs the [`CoreSchedule::FixedUpdate`] system in this system set.
FixedUpdate,
/// Responsible for doing most app logic. Systems should be registered here by default.
Update,
/// The copy of [`apply_system_buffers`] that runs immediately after `Update`.
UpdateFlush,
/// Runs after [`CoreSet::Update`].
PostUpdate,
/// The copy of [`apply_system_buffers`] that runs immediately after `PostUpdate`.
PostUpdateFlush,
/// Runs after all other members of this set.
Last,
/// The copy of [`apply_system_buffers`] that runs immediately after `Last`.
LastFlush,
}
impl CoreSet {
/// Sets up the base structure of [`CoreSchedule::Main`].
///
/// The sets defined in this enum are configured to run in order,
/// and a copy of [`apply_system_buffers`] is inserted into each `*Flush` set.
pub fn base_schedule() -> Schedule {
use CoreSet::*;
let mut schedule = Schedule::new();
// Create "stage-like" structure using buffer flushes + ordering
schedule
.set_default_base_set(Update)
.add_systems((
apply_system_buffers.in_base_set(FirstFlush),
apply_system_buffers.in_base_set(PreUpdateFlush),
apply_system_buffers.in_base_set(UpdateFlush),
apply_system_buffers.in_base_set(PostUpdateFlush),
apply_system_buffers.in_base_set(LastFlush),
))
.configure_sets(
(
First,
FirstFlush,
PreUpdate,
PreUpdateFlush,
StateTransitions,
FixedUpdate,
Update,
UpdateFlush,
PostUpdate,
PostUpdateFlush,
Last,
LastFlush,
)
.chain(),
);
schedule
}
}
/// The names of the default [`App`] startup sets, which live in [`CoreSchedule::Startup`].
///
/// The corresponding [`SystemSets`](bevy_ecs::schedule::SystemSet) are added by [`App::add_default_schedules`].
///
/// The `*Flush` sets are assigned to the copy of [`apply_system_buffers`]
/// that runs immediately after the matching system set.
/// These can be useful for ordering, but you almost never want to add your systems to these sets.
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
#[system_set(base)]
pub enum StartupSet {
/// Runs once before [`StartupSet::Startup`].
PreStartup,
/// The copy of [`apply_system_buffers`] that runs immediately after `PreStartup`.
PreStartupFlush,
/// Runs once when an [`App`] starts up.
Startup,
/// The copy of [`apply_system_buffers`] that runs immediately after `Startup`.
StartupFlush,
/// Runs once after [`StartupSet::Startup`].
PostStartup,
/// The copy of [`apply_system_buffers`] that runs immediately after `PostStartup`.
PostStartupFlush,
}
impl StartupSet {
/// Sets up the base structure of [`CoreSchedule::Startup`].
///
/// The sets defined in this enum are configured to run in order,
/// and a copy of [`apply_system_buffers`] is inserted into each `*Flush` set.
pub fn base_schedule() -> Schedule {
use StartupSet::*;
let mut schedule = Schedule::new();
schedule.set_default_base_set(Startup);
// Create "stage-like" structure using buffer flushes + ordering
schedule.add_systems((
apply_system_buffers.in_base_set(PreStartupFlush),
apply_system_buffers.in_base_set(StartupFlush),
apply_system_buffers.in_base_set(PostStartupFlush),
));
schedule.configure_sets(
(
PreStartup,
PreStartupFlush,
Startup,
StartupFlush,
PostStartup,
PostStartupFlush,
)
.chain(),
);
schedule
}
}

View file

@ -0,0 +1,168 @@
use crate::{App, Plugin};
use bevy_ecs::{
schedule::{ExecutorKind, Schedule, ScheduleLabel},
system::{Local, Resource},
world::{Mut, World},
};
/// The schedule that contains the app logic that is evaluated each tick of [`App::update()`].
///
/// By default, it will run the following schedules in the given order:
///
/// On the first run of the schedule (and only on the first run), it will run:
/// * [`PreStartup`]
/// * [`Startup`]
/// * [`PostStartup`]
///
/// Then it will run:
/// * [`First`]
/// * [`PreUpdate`]
/// * [`StateTransition`]
/// * [`RunFixedUpdateLoop`]
/// * This will run [`FixedUpdate`] zero to many times, based on how much time has elapsed.
/// * [`Update`]
/// * [`PostUpdate`]
/// * [`Last`]
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Main;
/// The schedule that runs before [`Startup`].
/// This is run by the [`Main`] schedule.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct PreStartup;
/// The schedule that runs once when the app starts.
/// This is run by the [`Main`] schedule.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Startup;
/// The schedule that runs once after [`Startup`].
/// This is run by the [`Main`] schedule.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct PostStartup;
/// Runs first in the schedule.
/// This is run by the [`Main`] schedule.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct First;
/// The schedule that contains logic that must run before [`Update`]. For example, a system that reads raw keyboard
/// input OS events into an `Events` resource. This enables systems in [`Update`] to consume the events from the `Events`
/// resource without actually knowing about (or taking a direct scheduler dependency on) the "os-level keyboard event sytsem".
///
/// [`PreUpdate`] exists to do "engine/plugin preparation work" that ensures the APIs consumed in [`Update`] are "ready".
/// [`PreUpdate`] abstracts out "pre work implementation details".
///
/// This is run by the [`Main`] schedule.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct PreUpdate;
/// Runs [state transitions](bevy_ecs::schedule::States).
/// This is run by the [`Main`] schedule.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct StateTransition;
/// Runs the [`FixedUpdate`] schedule in a loop according until all relevant elapsed time has been "consumed".
/// This is run by the [`Main`] schedule.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct RunFixedUpdateLoop;
/// The schedule that contains systems which only run after a fixed period of time has elapsed.
///
/// The exclusive `run_fixed_update_schedule` system runs this schedule.
/// This is run by the [`RunFixedUpdateLoop`] schedule.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FixedUpdate;
/// The schedule that contains app logic.
/// This is run by the [`Main`] schedule.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Update;
/// The schedule that contains logic that must run after [`Update`]. For example, synchronizing "local transforms" in a hierarchy
/// to "global" absolute transforms. This enables the [`PostUpdate`] transform-sync system to react to "local transform" changes in
/// [`Update`] without the [`Update`] systems needing to know about (or add scheduler dependencies for) the "global transform sync system".
///
/// [`PostUpdate`] exists to do "engine/plugin response work" to things that happened in [`Update`].
/// [`PostUpdate`] abstracts out "implementation details" from users defining systems in [`Update`].
///
/// This is run by the [`Main`] schedule.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct PostUpdate;
/// Runs last in the schedule.
/// This is run by the [`Main`] schedule.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Last;
/// Defines the schedules to be run for the [`Main`] schedule, including
/// their order.
#[derive(Resource, Debug)]
pub struct MainScheduleOrder {
/// The labels to run for the [`Main`] schedule (in the order they will be run).
pub labels: Vec<Box<dyn ScheduleLabel>>,
}
impl Default for MainScheduleOrder {
fn default() -> Self {
Self {
labels: vec![
Box::new(First),
Box::new(PreUpdate),
Box::new(StateTransition),
Box::new(RunFixedUpdateLoop),
Box::new(Update),
Box::new(PostUpdate),
Box::new(Last),
],
}
}
}
impl MainScheduleOrder {
/// Adds the given `schedule` after the `after` schedule
pub fn insert_after(&mut self, after: impl ScheduleLabel, schedule: impl ScheduleLabel) {
let index = self
.labels
.iter()
.position(|current| (**current).eq(&after))
.unwrap_or_else(|| panic!("Expected {after:?} to exist"));
self.labels.insert(index + 1, Box::new(schedule));
}
}
impl Main {
/// A system that runs the "main schedule"
pub fn run_main(world: &mut World, mut run_at_least_once: Local<bool>) {
if !*run_at_least_once {
let _ = world.try_run_schedule(PreStartup);
let _ = world.try_run_schedule(Startup);
let _ = world.try_run_schedule(PostStartup);
*run_at_least_once = true;
}
world.resource_scope(|world, order: Mut<MainScheduleOrder>| {
for label in &order.labels {
let _ = world.try_run_schedule_ref(&**label);
}
});
}
}
/// Initializes the [`Main`] schedule, sub schedules, and resources for a given [`App`].
pub struct MainSchedulePlugin;
impl Plugin for MainSchedulePlugin {
fn build(&self, app: &mut App) {
// simple "facilitator" schedules benefit from simpler single threaded scheduling
let mut main_schedule = Schedule::new();
main_schedule.set_executor_kind(ExecutorKind::SingleThreaded);
let mut fixed_update_loop_schedule = Schedule::new();
fixed_update_loop_schedule.set_executor_kind(ExecutorKind::SingleThreaded);
app.add_schedule(Main, main_schedule)
.add_schedule(RunFixedUpdateLoop, fixed_update_loop_schedule)
.init_resource::<MainScheduleOrder>()
.add_systems(Main, Main::run_main);
}
}

View file

@ -644,7 +644,7 @@ pub fn free_unused_assets_system(asset_server: Res<AssetServer>) {
mod test {
use super::*;
use crate::{loader::LoadedAsset, update_asset_storage_system};
use bevy_app::App;
use bevy_app::{App, Update};
use bevy_ecs::prelude::*;
use bevy_reflect::TypeUuid;
use bevy_utils::BoxedFuture;
@ -852,10 +852,13 @@ mod test {
let mut app = App::new();
app.insert_resource(assets);
app.insert_resource(asset_server);
app.add_systems((
free_unused_assets_system.in_set(FreeUnusedAssets),
update_asset_storage_system::<PngAsset>.after(FreeUnusedAssets),
));
app.add_systems(
Update,
(
free_unused_assets_system.in_set(FreeUnusedAssets),
update_asset_storage_system::<PngAsset>.after(FreeUnusedAssets),
),
);
fn load_asset(path: AssetPath, world: &World) -> HandleUntyped {
let asset_server = world.resource::<AssetServer>();

View file

@ -1,6 +1,6 @@
use crate::{
update_asset_storage_system, Asset, AssetLoader, AssetServer, AssetSet, Handle, HandleId,
RefChange, ReflectAsset, ReflectHandle,
update_asset_storage_system, Asset, AssetEvents, AssetLoader, AssetServer, Handle, HandleId,
LoadAssets, RefChange, ReflectAsset, ReflectHandle,
};
use bevy_app::{App, AppTypeRegistry};
use bevy_ecs::prelude::*;
@ -331,10 +331,8 @@ impl AddAsset for App {
};
self.insert_resource(assets)
.add_systems((
Assets::<T>::asset_event_system.in_base_set(AssetSet::AssetEvents),
update_asset_storage_system::<T>.in_base_set(AssetSet::LoadAssets),
))
.add_systems(LoadAssets, update_asset_storage_system::<T>)
.add_systems(AssetEvents, Assets::<T>::asset_event_system)
.register_type::<Handle<T>>()
.add_event::<AssetEvent<T>>()
}
@ -362,9 +360,9 @@ impl AddAsset for App {
{
#[cfg(feature = "debug_asset_server")]
{
self.add_system(
crate::debug_asset_server::sync_debug_assets::<T>
.in_base_set(bevy_app::CoreSet::Update),
self.add_systems(
bevy_app::Update,
crate::debug_asset_server::sync_debug_assets::<T>,
);
let mut app = self
.world

View file

@ -2,7 +2,7 @@
//!
//! Internal assets (e.g. shaders) are bundled directly into an application and can't be hot
//! reloaded using the conventional API.
use bevy_app::{App, Plugin};
use bevy_app::{App, Plugin, Update};
use bevy_ecs::{prelude::*, system::SystemState};
use bevy_tasks::{IoTaskPool, TaskPoolBuilder};
use bevy_utils::HashMap;
@ -75,7 +75,7 @@ impl Plugin for DebugAssetServerPlugin {
watch_for_changes: true,
});
app.insert_non_send_resource(DebugAssetApp(debug_asset_app));
app.add_system(run_debug_asset_app);
app.add_systems(Update, run_debug_asset_app);
}
}

View file

@ -18,8 +18,8 @@ impl<T: Asset> Default for AssetCountDiagnosticsPlugin<T> {
impl<T: Asset> Plugin for AssetCountDiagnosticsPlugin<T> {
fn build(&self, app: &mut App) {
app.add_startup_system(Self::setup_system)
.add_system(Self::diagnostic_system);
app.add_systems(Startup, Self::setup_system)
.add_systems(Update, Self::diagnostic_system);
}
}

View file

@ -46,18 +46,15 @@ pub use loader::*;
pub use path::*;
pub use reflect::*;
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_app::{prelude::*, MainScheduleOrder};
use bevy_ecs::schedule::ScheduleLabel;
/// [`SystemSet`]s for asset loading in an [`App`] schedule.
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
#[system_set(base)]
pub enum AssetSet {
/// Asset storages are updated.
LoadAssets,
/// Asset events are generated.
AssetEvents,
}
/// Asset storages are updated.
#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)]
pub struct LoadAssets;
/// Asset events are generated.
#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)]
pub struct AssetEvents;
/// Adds support for [`Assets`] to an App.
///
@ -106,24 +103,19 @@ impl Plugin for AssetPlugin {
app.insert_resource(asset_server);
}
app.register_type::<HandleId>();
app.configure_set(
AssetSet::LoadAssets
.before(CoreSet::PreUpdate)
.after(CoreSet::First),
)
.configure_set(
AssetSet::AssetEvents
.after(CoreSet::PostUpdate)
.before(CoreSet::Last),
)
.add_system(asset_server::free_unused_assets_system.in_base_set(CoreSet::PreUpdate));
app.register_type::<HandleId>()
.add_systems(PreUpdate, asset_server::free_unused_assets_system);
app.init_schedule(LoadAssets);
app.init_schedule(AssetEvents);
#[cfg(all(
feature = "filesystem_watcher",
all(not(target_arch = "wasm32"), not(target_os = "android"))
))]
app.add_system(io::filesystem_watcher_system.in_base_set(AssetSet::LoadAssets));
app.add_systems(LoadAssets, io::filesystem_watcher_system);
let mut order = app.world.resource_mut::<MainScheduleOrder>();
order.insert_after(First, LoadAssets);
order.insert_after(PostUpdate, AssetEvents);
}
}

View file

@ -4,13 +4,13 @@
//! # use bevy_ecs::{system::Res, event::EventWriter};
//! # use bevy_audio::{Audio, AudioPlugin};
//! # use bevy_asset::{AssetPlugin, AssetServer};
//! # use bevy_app::{App, AppExit, NoopPluginGroup as MinimalPlugins};
//! # use bevy_app::{App, AppExit, NoopPluginGroup as MinimalPlugins, Startup};
//! fn main() {
//! App::new()
//! .add_plugins(MinimalPlugins)
//! .add_plugin(AssetPlugin::default())
//! .add_plugin(AudioPlugin)
//! .add_startup_system(play_background_audio)
//! .add_systems(Startup, play_background_audio)
//! .run();
//! }
//!
@ -47,7 +47,6 @@ pub use sinks::*;
use bevy_app::prelude::*;
use bevy_asset::{AddAsset, Asset};
use bevy_ecs::prelude::*;
/// Adds support for audio playback to a Bevy Application
///
@ -62,7 +61,7 @@ impl Plugin for AudioPlugin {
.add_asset::<AudioSink>()
.add_asset::<SpatialAudioSink>()
.init_resource::<Audio<AudioSource>>()
.add_system(play_queued_audio_system::<AudioSource>.in_base_set(CoreSet::PostUpdate));
.add_systems(PostUpdate, play_queued_audio_system::<AudioSource>);
#[cfg(any(feature = "mp3", feature = "flac", feature = "wav", feature = "vorbis"))]
app.init_asset_loader::<AudioLoader>();
@ -78,6 +77,6 @@ impl AddAudioSource for App {
self.add_asset::<T>()
.init_resource::<Audio<T>>()
.init_resource::<AudioOutput<T>>()
.add_system(play_queued_audio_system::<T>.in_base_set(CoreSet::PostUpdate))
.add_systems(PostUpdate, play_queued_audio_system::<T>)
}
}

View file

@ -102,12 +102,12 @@ pub struct TaskPoolPlugin {
}
impl Plugin for TaskPoolPlugin {
fn build(&self, app: &mut App) {
fn build(&self, _app: &mut App) {
// Setup the default bevy task pools
self.task_pool_options.create_default_pools();
#[cfg(not(target_arch = "wasm32"))]
app.add_system(tick_global_task_pools.in_base_set(bevy_app::CoreSet::Last));
_app.add_systems(Last, tick_global_task_pools);
}
}
/// A dummy type that is [`!Send`](Send), to force systems to run on the main thread.
@ -124,7 +124,7 @@ fn tick_global_task_pools(_main_thread_marker: Option<NonSend<NonSendMarker>>) {
/// Maintains a count of frames rendered since the start of the application.
///
/// [`FrameCount`] is incremented during [`CoreSet::Last`], providing predictable
/// [`FrameCount`] is incremented during [`Last`], providing predictable
/// behaviour: it will be 0 during the first update, 1 during the next, and so forth.
///
/// # Overflows
@ -142,7 +142,7 @@ pub struct FrameCountPlugin;
impl Plugin for FrameCountPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<FrameCount>();
app.add_system(update_frame_count.in_base_set(CoreSet::Last));
app.add_systems(Last, update_frame_count);
}
}

View file

@ -7,13 +7,7 @@ pub use settings::{BloomCompositeMode, BloomPrefilterSettings, BloomSettings};
use crate::{core_2d, core_3d};
use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, HandleUntyped};
use bevy_ecs::{
prelude::{Component, Entity},
query::{QueryState, With},
schedule::IntoSystemConfig,
system::{Commands, Query, Res, ResMut},
world::World,
};
use bevy_ecs::prelude::*;
use bevy_math::UVec2;
use bevy_reflect::TypeUuid;
use bevy_render::{
@ -27,7 +21,7 @@ use bevy_render::{
renderer::{RenderContext, RenderDevice},
texture::{CachedTexture, TextureCache},
view::ViewTarget,
RenderApp, RenderSet,
Render, RenderApp, RenderSet,
};
#[cfg(feature = "trace")]
use bevy_utils::tracing::info_span;
@ -71,12 +65,15 @@ impl Plugin for BloomPlugin {
.init_resource::<BloomUpsamplingPipeline>()
.init_resource::<SpecializedRenderPipelines<BloomDownsamplingPipeline>>()
.init_resource::<SpecializedRenderPipelines<BloomUpsamplingPipeline>>()
.add_systems((
prepare_bloom_textures.in_set(RenderSet::Prepare),
prepare_downsampling_pipeline.in_set(RenderSet::Prepare),
prepare_upsampling_pipeline.in_set(RenderSet::Prepare),
queue_bloom_bind_groups.in_set(RenderSet::Queue),
));
.add_systems(
Render,
(
prepare_bloom_textures.in_set(RenderSet::Prepare),
prepare_downsampling_pipeline.in_set(RenderSet::Prepare),
prepare_upsampling_pipeline.in_set(RenderSet::Prepare),
queue_bloom_bind_groups.in_set(RenderSet::Queue),
),
);
// Add bloom to the 3d render graph
{

View file

@ -20,7 +20,7 @@ pub mod graph {
pub use camera_2d::*;
pub use main_pass_2d_node::*;
use bevy_app::{App, IntoSystemAppConfig, Plugin};
use bevy_app::{App, Plugin};
use bevy_ecs::prelude::*;
use bevy_render::{
camera::Camera,
@ -31,7 +31,7 @@ use bevy_render::{
DrawFunctionId, DrawFunctions, PhaseItem, RenderPhase,
},
render_resource::CachedRenderPipelineId,
Extract, ExtractSchedule, RenderApp, RenderSet,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_utils::FloatOrd;
use std::ops::Range;
@ -52,13 +52,16 @@ impl Plugin for Core2dPlugin {
render_app
.init_resource::<DrawFunctions<Transparent2d>>()
.add_systems((
extract_core_2d_camera_phases.in_schedule(ExtractSchedule),
sort_phase_system::<Transparent2d>.in_set(RenderSet::PhaseSort),
batch_phase_system::<Transparent2d>
.after(sort_phase_system::<Transparent2d>)
.in_set(RenderSet::PhaseSort),
));
.add_systems(ExtractSchedule, extract_core_2d_camera_phases)
.add_systems(
Render,
(
sort_phase_system::<Transparent2d>.in_set(RenderSet::PhaseSort),
batch_phase_system::<Transparent2d>
.after(sort_phase_system::<Transparent2d>)
.in_set(RenderSet::PhaseSort),
),
);
let pass_node_2d = MainPass2dNode::new(&mut render_app.world);
let tonemapping = TonemappingNode::new(&mut render_app.world);

View file

@ -23,7 +23,7 @@ use std::cmp::Reverse;
pub use camera_3d::*;
pub use main_pass_3d_node::*;
use bevy_app::{App, IntoSystemAppConfig, Plugin};
use bevy_app::{App, Plugin};
use bevy_ecs::prelude::*;
use bevy_render::{
camera::{Camera, ExtractedCamera},
@ -41,7 +41,7 @@ use bevy_render::{
renderer::RenderDevice,
texture::TextureCache,
view::ViewDepthTexture,
Extract, ExtractSchedule, RenderApp, RenderSet,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_utils::{FloatOrd, HashMap};
@ -68,15 +68,18 @@ impl Plugin for Core3dPlugin {
.init_resource::<DrawFunctions<Opaque3d>>()
.init_resource::<DrawFunctions<AlphaMask3d>>()
.init_resource::<DrawFunctions<Transparent3d>>()
.add_systems((
extract_core_3d_camera_phases.in_schedule(ExtractSchedule),
prepare_core_3d_depth_textures
.in_set(RenderSet::Prepare)
.after(bevy_render::view::prepare_windows),
sort_phase_system::<Opaque3d>.in_set(RenderSet::PhaseSort),
sort_phase_system::<AlphaMask3d>.in_set(RenderSet::PhaseSort),
sort_phase_system::<Transparent3d>.in_set(RenderSet::PhaseSort),
));
.add_systems(ExtractSchedule, extract_core_3d_camera_phases)
.add_systems(
Render,
(
prepare_core_3d_depth_textures
.in_set(RenderSet::Prepare)
.after(bevy_render::view::prepare_windows),
sort_phase_system::<Opaque3d>.in_set(RenderSet::PhaseSort),
sort_phase_system::<AlphaMask3d>.in_set(RenderSet::PhaseSort),
sort_phase_system::<Transparent3d>.in_set(RenderSet::PhaseSort),
),
);
let prepass_node = PrepassNode::new(&mut render_app.world);
let pass_node_3d = MainPass3dNode::new(&mut render_app.world);

View file

@ -14,7 +14,7 @@ use bevy_render::{
renderer::RenderDevice,
texture::BevyDefault,
view::{ExtractedView, ViewTarget},
RenderApp, RenderSet,
Render, RenderApp, RenderSet,
};
mod node;
@ -90,7 +90,7 @@ impl Plugin for FxaaPlugin {
render_app
.init_resource::<FxaaPipeline>()
.init_resource::<SpecializedRenderPipelines<FxaaPipeline>>()
.add_system(prepare_fxaa_pipelines.in_set(RenderSet::Prepare));
.add_systems(Render, prepare_fxaa_pipelines.in_set(RenderSet::Prepare));
{
let fxaa_node = FxaaNode::new(&mut render_app.world);

View file

@ -6,7 +6,7 @@ use bevy_render::{
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotInfo, SlotType},
renderer::RenderContext,
view::{Msaa, ViewTarget},
RenderSet,
Render, RenderSet,
};
use bevy_render::{render_resource::*, RenderApp};
@ -20,7 +20,10 @@ impl Plugin for MsaaWritebackPlugin {
return
};
render_app.add_system(queue_msaa_writeback_pipelines.in_set(RenderSet::Queue));
render_app.add_systems(
Render,
queue_msaa_writeback_pipelines.in_set(RenderSet::Queue),
);
let msaa_writeback_2d = MsaaWritebackNode::new(&mut render_app.world);
let msaa_writeback_3d = MsaaWritebackNode::new(&mut render_app.world);
let mut graph = render_app.world.resource_mut::<RenderGraph>();

View file

@ -10,7 +10,7 @@ use bevy_render::render_asset::RenderAssets;
use bevy_render::renderer::RenderDevice;
use bevy_render::texture::{CompressedImageFormats, Image, ImageSampler, ImageType};
use bevy_render::view::{ViewTarget, ViewUniform};
use bevy_render::{render_resource::*, RenderApp, RenderSet};
use bevy_render::{render_resource::*, Render, RenderApp, RenderSet};
mod node;
@ -94,7 +94,10 @@ impl Plugin for TonemappingPlugin {
render_app
.init_resource::<TonemappingPipeline>()
.init_resource::<SpecializedRenderPipelines<TonemappingPipeline>>()
.add_system(queue_view_tonemapping_pipelines.in_set(RenderSet::Queue));
.add_systems(
Render,
queue_view_tonemapping_pipelines.in_set(RenderSet::Queue),
);
}
}
}

View file

@ -3,7 +3,7 @@ use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_render::camera::{CameraOutputMode, ExtractedCamera};
use bevy_render::view::ViewTarget;
use bevy_render::{render_resource::*, RenderApp, RenderSet};
use bevy_render::{render_resource::*, Render, RenderApp, RenderSet};
mod node;
@ -14,7 +14,10 @@ pub struct UpscalingPlugin;
impl Plugin for UpscalingPlugin {
fn build(&self, app: &mut App) {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.add_system(queue_view_upscaling_pipelines.in_set(RenderSet::Queue));
render_app.add_systems(
Render,
queue_view_upscaling_pipelines.in_set(RenderSet::Queue),
);
}
}
}

View file

@ -9,8 +9,8 @@ pub struct EntityCountDiagnosticsPlugin;
impl Plugin for EntityCountDiagnosticsPlugin {
fn build(&self, app: &mut App) {
app.add_startup_system(Self::setup_system)
.add_system(Self::diagnostic_system);
app.add_systems(Startup, Self::setup_system)
.add_systems(Update, Self::diagnostic_system);
}
}

View file

@ -10,8 +10,8 @@ pub struct FrameTimeDiagnosticsPlugin;
impl Plugin for FrameTimeDiagnosticsPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.add_startup_system(Self::setup_system)
.add_system(Self::diagnostic_system);
app.add_systems(Startup, Self::setup_system)
.add_systems(Update, Self::diagnostic_system);
}
}

View file

@ -17,8 +17,10 @@ pub struct DiagnosticsPlugin;
impl Plugin for DiagnosticsPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<Diagnostics>()
.add_startup_system(system_information_diagnostics_plugin::internal::log_system_info);
app.init_resource::<Diagnostics>().add_systems(
Startup,
system_information_diagnostics_plugin::internal::log_system_info,
);
}
}

View file

@ -37,9 +37,9 @@ impl Plugin for LogDiagnosticsPlugin {
});
if self.debug {
app.add_system(Self::log_diagnostics_debug_system.in_base_set(CoreSet::PostUpdate));
app.add_systems(PostUpdate, Self::log_diagnostics_debug_system);
} else {
app.add_system(Self::log_diagnostics_system.in_base_set(CoreSet::PostUpdate));
app.add_systems(PostUpdate, Self::log_diagnostics_system);
}
}
}

View file

@ -14,8 +14,8 @@ use bevy_app::prelude::*;
pub struct SystemInformationDiagnosticsPlugin;
impl Plugin for SystemInformationDiagnosticsPlugin {
fn build(&self, app: &mut App) {
app.add_startup_system(internal::setup_system)
.add_system(internal::diagnostic_system);
app.add_systems(Startup, internal::setup_system)
.add_systems(Update, internal::diagnostic_system);
}
}

View file

@ -150,7 +150,7 @@ fn main() {
let mut schedule = Schedule::default();
// Add our system to the schedule
schedule.add_system(movement);
schedule.add_systems(movement);
// Run the schedule once. If your app has a "loop", you would run this once per loop
schedule.run(&mut world);

View file

@ -1,4 +1,4 @@
use bevy_ecs::{prelude::*, schedule::IntoSystemConfig};
use bevy_ecs::prelude::*;
use rand::Rng;
use std::ops::Deref;

View file

@ -16,7 +16,7 @@ fn main() {
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub struct FlushEvents;
schedule.add_system(Events::<MyEvent>::update_system.in_set(FlushEvents));
schedule.add_systems(Events::<MyEvent>::update_system.in_set(FlushEvents));
// Add systems sending and receiving events after the events are flushed.
schedule.add_systems((

View file

@ -469,7 +469,7 @@ pub fn derive_schedule_label(input: TokenStream) -> TokenStream {
}
/// Derive macro generating an impl of the trait `SystemSet`.
#[proc_macro_derive(SystemSet, attributes(system_set))]
#[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();

View file

@ -1,9 +1,5 @@
use proc_macro::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::parse::{Parse, ParseStream};
pub static SYSTEM_SET_ATTRIBUTE_NAME: &str = "system_set";
pub static BASE_ATTRIBUTE_NAME: &str = "base";
use quote::quote;
/// Derive a set trait
///
@ -12,55 +8,8 @@ pub static BASE_ATTRIBUTE_NAME: &str = "base";
/// - `input`: The [`syn::DeriveInput`] for the struct that we want to derive the set trait for
/// - `trait_path`: The [`syn::Path`] to the set trait
pub fn derive_set(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStream {
let mut base_trait_path = trait_path.clone();
let ident = &mut base_trait_path.segments.last_mut().unwrap().ident;
*ident = format_ident!("Base{ident}");
let mut free_trait_path = trait_path.clone();
let ident = &mut free_trait_path.segments.last_mut().unwrap().ident;
*ident = format_ident!("Free{ident}");
let ident = input.ident;
let mut is_base = false;
for attr in &input.attrs {
if !attr
.path
.get_ident()
.map_or(false, |ident| ident == SYSTEM_SET_ATTRIBUTE_NAME)
{
continue;
}
attr.parse_args_with(|input: ParseStream| {
let meta = input.parse_terminated::<syn::Meta, syn::token::Comma>(syn::Meta::parse)?;
for meta in meta {
let ident = meta.path().get_ident().unwrap_or_else(|| {
panic!(
"Unrecognized attribute: `{}`",
meta.path().to_token_stream()
)
});
if ident == BASE_ATTRIBUTE_NAME {
if let syn::Meta::Path(_) = meta {
is_base = true;
} else {
panic!(
"The `{BASE_ATTRIBUTE_NAME}` attribute is expected to have no value or arguments",
);
}
} else {
panic!(
"Unrecognized attribute: `{}`",
meta.path().to_token_stream()
);
}
}
Ok(())
})
.unwrap_or_else(|_| panic!("Invalid `{SYSTEM_SET_ATTRIBUTE_NAME}` attribute format"));
}
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(),
@ -73,28 +22,12 @@ pub fn derive_set(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStrea
.unwrap(),
);
let marker_impl = if is_base {
quote! {
impl #impl_generics #base_trait_path for #ident #ty_generics #where_clause {}
}
} else {
quote! {
impl #impl_generics #free_trait_path for #ident #ty_generics #where_clause {}
}
};
(quote! {
impl #impl_generics #trait_path for #ident #ty_generics #where_clause {
fn is_base(&self) -> bool {
#is_base
}
fn dyn_clone(&self) -> std::boxed::Box<dyn #trait_path> {
std::boxed::Box::new(std::clone::Clone::clone(self))
}
}
#marker_impl
})
.into()
}

View file

@ -939,7 +939,7 @@ mod tests {
world.send_event(TestEvent { i: 4 });
let mut schedule = Schedule::new();
schedule.add_system(|mut events: EventReader<TestEvent>| {
schedule.add_systems(|mut events: EventReader<TestEvent>| {
let mut iter = events.iter();
assert_eq!(iter.next(), Some(&TestEvent { i: 0 }));

View file

@ -39,9 +39,8 @@ pub mod prelude {
removal_detection::RemovedComponents,
schedule::{
apply_state_transition, apply_system_buffers, common_conditions::*, Condition,
IntoSystemConfig, IntoSystemConfigs, IntoSystemSet, IntoSystemSetConfig,
IntoSystemSetConfigs, NextState, OnEnter, OnExit, OnTransition, OnUpdate, Schedule,
Schedules, State, States, SystemSet,
IntoSystemConfigs, IntoSystemSet, IntoSystemSetConfig, IntoSystemSetConfigs, NextState,
OnEnter, OnExit, OnTransition, OnUpdate, Schedule, Schedules, State, States, SystemSet,
},
system::{
adapter as system_adapter,

View file

@ -31,7 +31,7 @@ pub trait Condition<Marker>: sealed::Condition<Marker> {
/// # let mut app = Schedule::new();
/// # let mut world = World::new();
/// # fn my_system() {}
/// app.add_system(
/// app.add_systems(
/// // The `resource_equals` run condition will panic since we don't initialize `R`,
/// // just like if we used `Res<R>` in a system.
/// my_system.run_if(resource_equals(R(0))),
@ -48,7 +48,7 @@ pub trait Condition<Marker>: sealed::Condition<Marker> {
/// # let mut app = Schedule::new();
/// # let mut world = World::new();
/// # fn my_system() {}
/// app.add_system(
/// app.add_systems(
/// // `resource_equals` will only get run if the resource `R` exists.
/// my_system.run_if(resource_exists::<R>().and_then(resource_equals(R(0)))),
/// );
@ -86,7 +86,7 @@ pub trait Condition<Marker>: sealed::Condition<Marker> {
/// # let mut world = World::new();
/// # #[derive(Resource)] struct C(bool);
/// # fn my_system(mut c: ResMut<C>) { c.0 = true; }
/// app.add_system(
/// app.add_systems(
/// // Only run the system if either `A` or `B` exist.
/// my_system.run_if(resource_exists::<A>().or_else(resource_exists::<B>())),
/// );
@ -159,7 +159,7 @@ pub mod common_conditions {
/// # let mut app = Schedule::new();
/// # let mut world = World::new();
/// # world.init_resource::<Counter>();
/// app.add_system(
/// app.add_systems(
/// // `run_once` will only return true the first time it's evaluated
/// my_system.run_if(run_once()),
/// );
@ -199,7 +199,7 @@ pub mod common_conditions {
/// # struct Counter(u8);
/// # let mut app = Schedule::new();
/// # let mut world = World::new();
/// app.add_system(
/// app.add_systems(
/// // `resource_exsists` will only return true if the given resource exsists in the world
/// my_system.run_if(resource_exists::<Counter>()),
/// );
@ -239,7 +239,7 @@ pub mod common_conditions {
/// # let mut app = Schedule::new();
/// # let mut world = World::new();
/// # world.init_resource::<Counter>();
/// app.add_system(
/// app.add_systems(
/// // `resource_equals` will only return true if the given resource equals the given value
/// my_system.run_if(resource_equals(Counter(0))),
/// );
@ -276,7 +276,7 @@ pub mod common_conditions {
/// # struct Counter(u8);
/// # let mut app = Schedule::new();
/// # let mut world = World::new();
/// app.add_system(
/// app.add_systems(
/// // `resource_exists_and_equals` will only return true
/// // if the given resource exsists and equals the given value
/// my_system.run_if(resource_exists_and_equals(Counter(0))),
@ -319,7 +319,7 @@ pub mod common_conditions {
/// # struct Counter(u8);
/// # let mut app = Schedule::new();
/// # let mut world = World::new();
/// app.add_system(
/// app.add_systems(
/// // `resource_added` will only return true if the
/// // given resource was just added
/// my_system.run_if(resource_added::<Counter>()),
@ -370,7 +370,7 @@ pub mod common_conditions {
/// # let mut app = Schedule::new();
/// # let mut world = World::new();
/// # world.init_resource::<Counter>();
/// app.add_system(
/// app.add_systems(
/// // `resource_changed` will only return true if the
/// // given resource was just changed (or added)
/// my_system.run_if(
@ -423,7 +423,7 @@ pub mod common_conditions {
/// # struct Counter(u8);
/// # let mut app = Schedule::new();
/// # let mut world = World::new();
/// app.add_system(
/// app.add_systems(
/// // `resource_exists_and_changed` will only return true if the
/// // given resource exsists and was just changed (or added)
/// my_system.run_if(
@ -485,7 +485,7 @@ pub mod common_conditions {
/// # let mut app = Schedule::new();
/// # let mut world = World::new();
/// # world.init_resource::<Counter>();
/// app.add_system(
/// app.add_systems(
/// // `resource_changed_or_removed` will only return true if the
/// // given resource was just changed or removed (or added)
/// my_system.run_if(
@ -555,7 +555,7 @@ pub mod common_conditions {
/// # let mut app = Schedule::new();
/// # let mut world = World::new();
/// # world.init_resource::<Counter>();
/// app.add_system(
/// app.add_systems(
/// // `resource_removed` will only return true if the
/// // given resource was just removed
/// my_system.run_if(resource_removed::<MyResource>()),
@ -617,7 +617,7 @@ pub mod common_conditions {
/// Paused,
/// }
///
/// app.add_system(
/// app.add_systems(
/// // `state_exists` will only return true if the
/// // given state exsists
/// my_system.run_if(state_exists::<GameState>()),
@ -784,7 +784,7 @@ pub mod common_conditions {
///
/// world.init_resource::<State<GameState>>();
///
/// app.add_system(
/// app.add_systems(
/// // `state_changed` will only return true if the
/// // given states value has just been updated or
/// // the state has just been added
@ -826,9 +826,9 @@ pub mod common_conditions {
/// # let mut world = World::new();
/// # world.init_resource::<Counter>();
/// # world.init_resource::<Events<MyEvent>>();
/// # app.add_system(Events::<MyEvent>::update_system.before(my_system));
/// # app.add_systems(Events::<MyEvent>::update_system.before(my_system));
///
/// app.add_system(
/// app.add_systems(
/// my_system.run_if(on_event::<MyEvent>()),
/// );
///
@ -868,7 +868,7 @@ pub mod common_conditions {
/// # let mut app = Schedule::new();
/// # let mut world = World::new();
/// # world.init_resource::<Counter>();
/// app.add_system(
/// app.add_systems(
/// my_system.run_if(any_with_component::<MyComponent>()),
/// );
///
@ -904,7 +904,7 @@ pub mod common_conditions {
/// # let mut app = Schedule::new();
/// # let mut world = World::new();
/// # world.init_resource::<Counter>();
/// app.add_system(
/// app.add_systems(
/// // `not` will inverse any condition you pass in.
/// // Since the condition we choose always returns true
/// // this system will never run
@ -1073,15 +1073,11 @@ where
mod tests {
use super::{common_conditions::*, Condition};
use crate as bevy_ecs;
use crate::{
change_detection::ResMut,
component::Component,
schedule::{
common_conditions::not, IntoSystemConfig, IntoSystemConfigs, Schedule, State, States,
},
system::Local,
world::World,
};
use crate::component::Component;
use crate::schedule::IntoSystemConfigs;
use crate::schedule::{common_conditions::not, State, States};
use crate::system::Local;
use crate::{change_detection::ResMut, schedule::Schedule, world::World};
use bevy_ecs_macros::Resource;
#[derive(Resource, Default)]
@ -1103,7 +1099,7 @@ mod tests {
let mut schedule = Schedule::new();
// Run every other cycle
schedule.add_system(increment_counter.run_if(every_other_time));
schedule.add_systems(increment_counter.run_if(every_other_time));
schedule.run(&mut world);
schedule.run(&mut world);
@ -1113,7 +1109,7 @@ mod tests {
assert_eq!(world.resource::<Counter>().0, 2);
// Run every other cycle oppsite to the last one
schedule.add_system(increment_counter.run_if(not(every_other_time)));
schedule.add_systems(increment_counter.run_if(not(every_other_time)));
schedule.run(&mut world);
schedule.run(&mut world);
@ -1130,9 +1126,9 @@ mod tests {
let mut schedule = Schedule::new();
// Always run
schedule.add_system(increment_counter.run_if(every_other_time.or_else(|| true)));
schedule.add_systems(increment_counter.run_if(every_other_time.or_else(|| true)));
// Run every other cycle
schedule.add_system(increment_counter.run_if(every_other_time.and_then(|| true)));
schedule.add_systems(increment_counter.run_if(every_other_time.and_then(|| true)));
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0, 2);
@ -1147,9 +1143,9 @@ mod tests {
let mut schedule = Schedule::new();
// Run every other cycle
schedule.add_system(increment_counter.run_if(every_other_time).run_if(|| true));
schedule.add_systems(increment_counter.run_if(every_other_time).run_if(|| true));
// Never run
schedule.add_system(increment_counter.run_if(every_other_time).run_if(|| false));
schedule.add_systems(increment_counter.run_if(every_other_time).run_if(|| false));
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0, 1);
@ -1166,7 +1162,7 @@ mod tests {
// This should never run, if multiple run conditions worked
// like an OR condition then it would always run
schedule.add_system(
schedule.add_systems(
increment_counter
.run_if(every_other_time)
.run_if(not(every_other_time)),

View file

@ -5,57 +5,11 @@ use crate::{
condition::{BoxedCondition, Condition},
graph_utils::{Ambiguity, Dependency, DependencyKind, GraphInfo},
set::{BoxedSystemSet, IntoSystemSet, SystemSet},
ScheduleLabel,
},
system::{BoxedSystem, IntoSystem, System},
};
use super::{BaseSystemSet, FreeSystemSet};
/// 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.system_type().is_none(),
"configuring system type sets is not allowed"
);
Self {
set,
graph_info: GraphInfo::system_set(),
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();
let mut graph_info = GraphInfo::system();
graph_info.sets = sets;
Self {
system,
graph_info,
conditions: Vec::new(),
}
}
}
fn new_condition<M>(condition: impl Condition<M>) -> BoxedCondition {
let condition_system = IntoSystem::into_system(condition);
assert!(
@ -79,6 +33,370 @@ fn ambiguous_with(graph_info: &mut GraphInfo, set: BoxedSystemSet) {
}
}
impl<Marker, F> IntoSystemConfigs<Marker> for F
where
F: IntoSystem<(), (), Marker>,
{
fn into_configs(self) -> SystemConfigs {
SystemConfigs::new_system(Box::new(IntoSystem::into_system(self)))
}
}
impl IntoSystemConfigs<()> for BoxedSystem<(), ()> {
fn into_configs(self) -> SystemConfigs {
SystemConfigs::new_system(self)
}
}
pub struct SystemConfig {
pub(crate) system: BoxedSystem,
pub(crate) graph_info: GraphInfo,
pub(crate) conditions: Vec<BoxedCondition>,
}
/// A collection of [`SystemConfig`].
pub enum SystemConfigs {
SystemConfig(SystemConfig),
Configs {
configs: Vec<SystemConfigs>,
/// If `true`, adds `before -> after` ordering constraints between the successive elements.
chained: bool,
},
}
impl SystemConfigs {
fn new_system(system: BoxedSystem) -> Self {
// include system in its default sets
let sets = system.default_system_sets().into_iter().collect();
Self::SystemConfig(SystemConfig {
system,
graph_info: GraphInfo {
sets,
..Default::default()
},
conditions: Vec::new(),
})
}
fn in_set_inner(&mut self, set: BoxedSystemSet) {
match self {
SystemConfigs::SystemConfig(config) => {
config.graph_info.sets.push(set);
}
SystemConfigs::Configs { configs, .. } => {
for config in configs {
config.in_set_inner(set.dyn_clone());
}
}
}
}
fn before_inner(&mut self, set: BoxedSystemSet) {
match self {
SystemConfigs::SystemConfig(config) => {
config
.graph_info
.dependencies
.push(Dependency::new(DependencyKind::Before, set));
}
SystemConfigs::Configs { configs, .. } => {
for config in configs {
config.before_inner(set.dyn_clone());
}
}
}
}
fn after_inner(&mut self, set: BoxedSystemSet) {
match self {
SystemConfigs::SystemConfig(config) => {
config
.graph_info
.dependencies
.push(Dependency::new(DependencyKind::After, set));
}
SystemConfigs::Configs { configs, .. } => {
for config in configs {
config.after_inner(set.dyn_clone());
}
}
}
}
fn distributive_run_if_inner<M>(&mut self, condition: impl Condition<M> + Clone) {
match self {
SystemConfigs::SystemConfig(config) => {
config.conditions.push(new_condition(condition));
}
SystemConfigs::Configs { configs, .. } => {
for config in configs {
config.distributive_run_if_inner(condition.clone());
}
}
}
}
fn ambiguous_with_inner(&mut self, set: BoxedSystemSet) {
match self {
SystemConfigs::SystemConfig(config) => {
ambiguous_with(&mut config.graph_info, set);
}
SystemConfigs::Configs { configs, .. } => {
for config in configs {
config.ambiguous_with_inner(set.dyn_clone());
}
}
}
}
fn ambiguous_with_all_inner(&mut self) {
match self {
SystemConfigs::SystemConfig(config) => {
config.graph_info.ambiguous_with = Ambiguity::IgnoreAll;
}
SystemConfigs::Configs { configs, .. } => {
for config in configs {
config.ambiguous_with_all_inner();
}
}
}
}
fn run_if_inner(&mut self, condition: BoxedCondition) {
match self {
SystemConfigs::SystemConfig(config) => {
config.conditions.push(condition);
}
SystemConfigs::Configs { .. } => {
todo!("run_if is not implemented for groups of systems yet")
}
}
}
}
/// Types that can convert into a [`SystemConfigs`].
pub trait IntoSystemConfigs<Marker>
where
Self: Sized,
{
/// Convert into a [`SystemConfigs`].
#[doc(hidden)]
fn into_configs(self) -> SystemConfigs;
/// Add these systems to the provided `set`.
#[track_caller]
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)
}
/// Add a run condition to each contained system.
///
/// Each system will receive its own clone of the [`Condition`] and will only run
/// if the `Condition` is true.
///
/// Each individual condition will be evaluated at most once (per schedule run),
/// right before the corresponding system prepares to run.
///
/// This is equivalent to calling [`run_if`](IntoSystemConfigs::run_if) on each individual
/// system, as shown below:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut app = Schedule::new();
/// # fn a() {}
/// # fn b() {}
/// # fn condition() -> bool { true }
/// app.add_systems((a, b).distributive_run_if(condition));
/// app.add_systems((a.run_if(condition), b.run_if(condition)));
/// ```
///
/// # Note
///
/// Because the conditions are evaluated separately for each system, there is no guarantee
/// that all evaluations in a single schedule run will yield the same result. If another
/// system is run inbetween two evaluations it could cause the result of the condition to change.
///
/// Use [`run_if`](IntoSystemSetConfig::run_if) on a [`SystemSet`] if you want to make sure
/// that either all or none of the systems are run, or you don't want to evaluate the run
/// condition for each contained system separately.
fn distributive_run_if<M>(self, condition: impl Condition<M> + Clone) -> SystemConfigs {
self.into_configs().distributive_run_if(condition)
}
/// Run the systems 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<M>(self, condition: impl Condition<M>) -> SystemConfigs {
self.into_configs().run_if(condition)
}
/// 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()
}
/// This used to add the system to `CoreSchedule::Startup`.
/// This was a shorthand for `self.in_schedule(CoreSchedule::Startup)`.
///
/// # Panics
///
/// Always panics. Please migrate to the new `App::add_systems` with the `Startup` schedule:
/// Ex: `app.add_system(foo.on_startup())` -> `app.add_systems(Startup, foo)`
#[deprecated(
since = "0.11.0",
note = "`app.add_system(foo.on_startup())` has been deprecated in favor of `app.add_systems(Startup, foo)`. Please migrate to that API."
)]
fn on_startup(self) -> SystemConfigs {
panic!("`app.add_system(foo.on_startup())` has been deprecated in favor of `app.add_systems(Startup, foo)`. Please migrate to that API.");
}
/// This used to add the system to the provided `schedule`.
///
/// # Panics
///
/// Always panics. Please migrate to the new `App::add_systems`:
/// Ex: `app.add_system(foo.in_schedule(SomeSchedule))` -> `app.add_systems(SomeSchedule, foo)`
#[deprecated(
since = "0.11.0",
note = "`app.add_system(foo.in_schedule(SomeSchedule))` has been deprecated in favor of `app.add_systems(SomeSchedule, foo)`. Please migrate to that API."
)]
fn in_schedule(self, _schedule: impl ScheduleLabel) -> SystemConfigs {
panic!("`app.add_system(foo.in_schedule(SomeSchedule))` has been deprecated in favor of `app.add_systems(SomeSchedule, foo)`. Please migrate to that API.");
}
}
impl IntoSystemConfigs<()> for SystemConfigs {
fn into_configs(self) -> Self {
self
}
#[track_caller]
fn in_set(mut self, set: impl SystemSet) -> Self {
assert!(
set.system_type().is_none(),
"adding arbitrary systems to a system type set is not allowed"
);
self.in_set_inner(set.dyn_clone());
self
}
fn before<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
let set = set.into_system_set();
self.before_inner(set.dyn_clone());
self
}
fn after<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
let set = set.into_system_set();
self.after_inner(set.dyn_clone());
self
}
fn distributive_run_if<M>(mut self, condition: impl Condition<M> + Clone) -> SystemConfigs {
self.distributive_run_if_inner(condition);
self
}
fn ambiguous_with<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
let set = set.into_system_set();
self.ambiguous_with_inner(set.dyn_clone());
self
}
fn ambiguous_with_all(mut self) -> Self {
self.ambiguous_with_all_inner();
self
}
fn run_if<M>(mut self, condition: impl Condition<M>) -> SystemConfigs {
self.run_if_inner(new_condition(condition));
self
}
fn chain(mut self) -> Self {
match &mut self {
SystemConfigs::SystemConfig(_) => { /* no op */ }
SystemConfigs::Configs { chained, .. } => {
*chained = true;
}
}
self
}
}
pub struct SystemConfigTupleMarker;
macro_rules! impl_system_collection {
($(($param: ident, $sys: ident)),*) => {
impl<$($param, $sys),*> IntoSystemConfigs<(SystemConfigTupleMarker, $($param,)*)> for ($($sys,)*)
where
$($sys: IntoSystemConfigs<$param>),*
{
#[allow(non_snake_case)]
fn into_configs(self) -> SystemConfigs {
let ($($sys,)*) = self;
SystemConfigs::Configs {
configs: vec![$($sys.into_configs(),)*],
chained: false,
}
}
}
}
}
all_tuples!(impl_system_collection, 1, 20, P, S);
/// 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.system_type().is_none(),
"configuring system type sets is not allowed"
);
Self {
set,
graph_info: GraphInfo::default(),
conditions: Vec::new(),
}
}
}
/// Types that can be converted into a [`SystemSetConfig`].
///
/// This has been implemented for all types that implement [`SystemSet`] and boxed trait objects.
@ -88,18 +406,9 @@ pub trait IntoSystemSetConfig: Sized {
fn into_config(self) -> SystemSetConfig;
/// Add to the provided `set`.
#[track_caller]
fn in_set(self, set: impl FreeSystemSet) -> SystemSetConfig {
fn in_set(self, set: impl SystemSet) -> SystemSetConfig {
self.into_config().in_set(set)
}
/// Add to the provided "base" `set`. For more information on base sets, see [`SystemSet::is_base`].
#[track_caller]
fn in_base_set(self, set: impl BaseSystemSet) -> SystemSetConfig {
self.into_config().in_base_set(set)
}
/// Add this set to the schedules's default base set.
fn in_default_base_set(self) -> SystemSetConfig {
self.into_config().in_default_base_set()
}
/// Run before all systems in `set`.
fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig {
self.into_config().before(set)
@ -125,6 +434,35 @@ pub trait IntoSystemSetConfig: Sized {
fn ambiguous_with_all(self) -> SystemSetConfig {
self.into_config().ambiguous_with_all()
}
/// This used to configure the set in the `CoreSchedule::Startup` schedule.
/// This was a shorthand for `self.in_schedule(CoreSchedule::Startup)`.
///
/// # Panics
///
/// Always panics. Please migrate to the new `App::configure_set` with the `Startup` schedule:
/// Ex: `app.configure_set(MySet.on_startup())` -> `app.configure_set(Startup, MySet)`
#[deprecated(
since = "0.11.0",
note = "`app.configure_set(MySet.on_startup())` has been deprecated in favor of `app.configure_set(Startup, MySet)`. Please migrate to that API."
)]
fn on_startup(self) -> SystemSetConfigs {
panic!("`app.configure_set(MySet.on_startup())` has been deprecated in favor of `app.configure_set(Startup, MySet)`. Please migrate to that API.");
}
/// This used to configure the set in the provided `schedule`.
///
/// # Panics
///
/// Always panics. Please migrate to the new `App::configure_set`:
/// Ex: `app.configure_set(MySet.in_schedule(SomeSchedule))` -> `app.configure_set(SomeSchedule, MySet)`
#[deprecated(
since = "0.11.0",
note = "`app.configure_set(MySet.in_schedule(SomeSchedule))` has been deprecated in favor of `app.configure_set(SomeSchedule, MySet)`. Please migrate to that API."
)]
fn in_schedule(self, _schedule: impl ScheduleLabel) -> SystemSetConfigs {
panic!("`app.configure_set(MySet.in_schedule(SomeSchedule))` has been deprecated in favor of `app.configure_set(SomeSchedule, MySet)`. Please migrate to that API.");
}
}
impl<S: SystemSet> IntoSystemSetConfig for S {
@ -150,41 +488,10 @@ impl IntoSystemSetConfig for SystemSetConfig {
set.system_type().is_none(),
"adding arbitrary systems to a system type set is not allowed"
);
assert!(
!set.is_base(),
"Sets cannot be added to 'base' system sets using 'in_set'. Use 'in_base_set' instead."
);
assert!(
!self.set.is_base(),
"Base system sets cannot be added to other sets."
);
self.graph_info.sets.push(Box::new(set));
self
}
#[track_caller]
fn in_base_set(mut self, set: impl SystemSet) -> Self {
assert!(
set.system_type().is_none(),
"System type sets cannot be base sets."
);
assert!(
set.is_base(),
"Sets cannot be added to normal sets using 'in_base_set'. Use 'in_set' instead."
);
assert!(
!self.set.is_base(),
"Base system sets cannot be added to other sets."
);
self.graph_info.set_base_set(Box::new(set));
self
}
fn in_default_base_set(mut self) -> SystemSetConfig {
self.graph_info.add_default_base_set = true;
self
}
fn before<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
self.graph_info.dependencies.push(Dependency::new(
DependencyKind::Before,
@ -217,332 +524,6 @@ impl IntoSystemSetConfig for SystemSetConfig {
}
}
/// 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<Marker, Config = SystemConfig>: Sized
where
Config: IntoSystemConfig<(), Config>,
{
/// Convert into a [`SystemConfig`].
#[doc(hidden)]
fn into_config(self) -> Config;
/// Add to `set` membership.
#[track_caller]
fn in_set(self, set: impl FreeSystemSet) -> Config {
self.into_config().in_set(set)
}
/// Add to the provided "base" `set`. For more information on base sets, see [`SystemSet::is_base`].
#[track_caller]
fn in_base_set(self, set: impl BaseSystemSet) -> Config {
self.into_config().in_base_set(set)
}
/// Don't add this system to the schedules's default set.
fn no_default_base_set(self) -> Config {
self.into_config().no_default_base_set()
}
/// Run before all systems in `set`.
fn before<M>(self, set: impl IntoSystemSet<M>) -> Config {
self.into_config().before(set)
}
/// Run after all systems in `set`.
fn after<M>(self, set: impl IntoSystemSet<M>) -> Config {
self.into_config().after(set)
}
/// 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<M>(self, condition: impl Condition<M>) -> Config {
self.into_config().run_if(condition)
}
/// 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>) -> Config {
self.into_config().ambiguous_with(set)
}
/// 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) -> Config {
self.into_config().ambiguous_with_all()
}
}
impl<Marker, F> IntoSystemConfig<Marker> for F
where
F: IntoSystem<(), (), Marker>,
{
fn into_config(self) -> SystemConfig {
SystemConfig::new(Box::new(IntoSystem::into_system(self)))
}
}
impl IntoSystemConfig<()> for BoxedSystem<(), ()> {
fn into_config(self) -> SystemConfig {
SystemConfig::new(self)
}
}
impl IntoSystemConfig<()> for SystemConfig {
fn into_config(self) -> Self {
self
}
#[track_caller]
fn in_set(mut self, set: impl SystemSet) -> Self {
assert!(
set.system_type().is_none(),
"adding arbitrary systems to a system type set is not allowed"
);
assert!(
!set.is_base(),
"Systems cannot be added to 'base' system sets using 'in_set'. Use 'in_base_set' instead."
);
self.graph_info.sets.push(Box::new(set));
self
}
#[track_caller]
fn in_base_set(mut self, set: impl SystemSet) -> Self {
assert!(
set.system_type().is_none(),
"System type sets cannot be base sets."
);
assert!(
set.is_base(),
"Systems cannot be added to normal sets using 'in_base_set'. Use 'in_set' instead."
);
self.graph_info.set_base_set(Box::new(set));
self
}
fn no_default_base_set(mut self) -> SystemConfig {
self.graph_info.add_default_base_set = false;
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<M>(mut self, condition: impl Condition<M>) -> 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
}
}
/// 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<Marker>
where
Self: Sized,
{
/// Convert into a [`SystemConfigs`].
#[doc(hidden)]
fn into_configs(self) -> SystemConfigs;
/// Add these systems to the provided `set`.
#[track_caller]
fn in_set(self, set: impl FreeSystemSet) -> SystemConfigs {
self.into_configs().in_set(set)
}
/// Add these systems to the provided "base" `set`. For more information on base sets, see [`SystemSet::is_base`].
#[track_caller]
fn in_base_set(self, set: impl BaseSystemSet) -> SystemConfigs {
self.into_configs().in_base_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)
}
/// Add a run condition to each contained system.
///
/// The [`Condition`] must be [`Clone`]. Each system will receive its own clone
/// of the `Condition` and will only run if the `Condition` is true.
///
/// Each individual condition will be evaluated at most once (per schedule run),
/// right before the corresponding system prepares to run.
///
/// This is equivalent to calling [`run_if`](IntoSystemConfig::run_if) on each individual
/// system, as shown below:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut app = Schedule::new();
/// # fn a() {}
/// # fn b() {}
/// # fn condition() -> bool { true }
/// app.add_systems((a, b).distributive_run_if(condition));
/// app.add_systems((a.run_if(condition), b.run_if(condition)));
/// ```
///
/// # Note
///
/// Because the conditions are evaluated separately for each system, there is no guarantee
/// that all evaluations in a single schedule run will yield the same result. If another
/// system is run inbetween two evaluations it could cause the result of the condition to change.
///
/// Use [`run_if`](IntoSystemSetConfig::run_if) on a [`SystemSet`] if you want to make sure
/// that either all or none of the systems are run, or you don't want to evaluate the run
/// condition for each contained system separately.
///
/// The [`Condition`] is cloned for each system.
/// Cloned instances of [`FunctionSystem`](crate::system::FunctionSystem) will be de-initialized.
fn distributive_run_if<M>(self, condition: impl Condition<M> + Clone) -> SystemConfigs {
self.into_configs().distributive_run_if(condition)
}
/// 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
}
#[track_caller]
fn in_set(mut self, set: impl SystemSet) -> Self {
assert!(
set.system_type().is_none(),
"adding arbitrary systems to a system type set is not allowed"
);
assert!(
!set.is_base(),
"Systems cannot be added to 'base' system sets using 'in_set'. Use 'in_base_set' instead."
);
for config in &mut self.systems {
config.graph_info.sets.push(set.dyn_clone());
}
self
}
#[track_caller]
fn in_base_set(mut self, set: impl SystemSet) -> Self {
assert!(
set.system_type().is_none(),
"System type sets cannot be base sets."
);
assert!(
set.is_base(),
"Systems cannot be added to normal sets using 'in_base_set'. Use 'in_set' instead."
);
for config in &mut self.systems {
config.graph_info.set_base_set(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 distributive_run_if<M>(mut self, condition: impl Condition<M> + Clone) -> SystemConfigs {
for config in &mut self.systems {
config.conditions.push(new_condition(condition.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>,
@ -561,16 +542,10 @@ where
/// Add these system sets to the provided `set`.
#[track_caller]
fn in_set(self, set: impl FreeSystemSet) -> SystemSetConfigs {
fn in_set(self, set: impl SystemSet) -> SystemSetConfigs {
self.into_configs().in_set(set)
}
/// Add these system sets to the provided "base" `set`. For more information on base sets, see [`SystemSet::is_base`].
#[track_caller]
fn in_base_set(self, set: impl BaseSystemSet) -> SystemSetConfigs {
self.into_configs().in_base_set(set)
}
/// Run before all systems in `set`.
fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfigs {
self.into_configs().before(set)
@ -599,6 +574,35 @@ where
fn chain(self) -> SystemSetConfigs {
self.into_configs().chain()
}
/// This used to configure the sets in the `CoreSchedule::Startup` schedule.
/// This was a shorthand for `self.in_schedule(CoreSchedule::Startup)`.
///
/// # Panics
///
/// Always panics. Please migrate to the new `App::configure_sets` with the `Startup` schedule:
/// Ex: `app.configure_sets((A, B).on_startup())` -> `app.configure_sets(Startup, (A, B))`
#[deprecated(
since = "0.11.0",
note = "`app.configure_sets((A, B).on_startup())` has been deprecated in favor of `app.configure_sets(Startup, (A, B))`. Please migrate to that API."
)]
fn on_startup(self) -> SystemSetConfigs {
panic!("`app.configure_sets((A, B).on_startup())` has been deprecated in favor of `app.configure_sets(Startup, (A, B))`. Please migrate to that API.");
}
/// This used to configure the sets in the provided `schedule`.
///
/// # Panics
///
/// Always panics. Please migrate to the new `App::configure_set`:
/// Ex: `app.configure_sets((A, B).in_schedule(SomeSchedule))` -> `app.configure_sets(SomeSchedule, (A, B))`
#[deprecated(
since = "0.11.0",
note = "`app.configure_sets((A, B).in_schedule(SomeSchedule))` has been deprecated in favor of `app.configure_sets(SomeSchedule, (A, B))`. Please migrate to that API."
)]
fn in_schedule(self, _schedule: impl ScheduleLabel) -> SystemSetConfigs {
panic!("`app.configure_sets((A, B).in_schedule(SomeSchedule))` has been deprecated in favor of `app.configure_sets(SomeSchedule, (A, B))`. Please migrate to that API.");
}
}
impl IntoSystemSetConfigs for SystemSetConfigs {
@ -612,42 +616,13 @@ impl IntoSystemSetConfigs for SystemSetConfigs {
set.system_type().is_none(),
"adding arbitrary systems to a system type set is not allowed"
);
assert!(
!set.is_base(),
"Sets cannot be added to 'base' system sets using 'in_set'. Use 'in_base_set' instead."
);
for config in &mut self.sets {
assert!(
!config.set.is_base(),
"Base system sets cannot be added to other sets."
);
config.graph_info.sets.push(set.dyn_clone());
}
self
}
#[track_caller]
fn in_base_set(mut self, set: impl SystemSet) -> Self {
assert!(
set.system_type().is_none(),
"System type sets cannot be base sets."
);
assert!(
set.is_base(),
"Sets cannot be added to normal sets using 'in_base_set'. Use 'in_set' instead."
);
for config in &mut self.sets {
assert!(
!config.set.is_base(),
"Base system sets cannot be added to other sets."
);
config.graph_info.set_base_set(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 {
@ -695,24 +670,6 @@ impl IntoSystemSetConfigs for SystemSetConfigs {
}
}
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,)*)
@ -729,5 +686,4 @@ macro_rules! impl_system_set_collection {
}
}
all_tuples!(impl_system_collection, 0, 15, P, S);
all_tuples!(impl_system_set_collection, 0, 15, S);

View file

@ -67,56 +67,14 @@ pub(crate) enum Ambiguity {
IgnoreAll,
}
#[derive(Clone)]
#[derive(Clone, Default)]
pub(crate) struct GraphInfo {
pub(crate) sets: Vec<BoxedSystemSet>,
pub(crate) dependencies: Vec<Dependency>,
pub(crate) ambiguous_with: Ambiguity,
pub(crate) add_default_base_set: bool,
pub(crate) base_set: Option<BoxedSystemSet>,
}
impl Default for GraphInfo {
fn default() -> Self {
GraphInfo {
sets: Vec::new(),
base_set: None,
dependencies: Vec::new(),
ambiguous_with: Ambiguity::default(),
add_default_base_set: true,
}
}
}
impl GraphInfo {
pub(crate) fn system() -> GraphInfo {
GraphInfo {
// systems get the default base set automatically
add_default_base_set: true,
..Default::default()
}
}
pub(crate) fn system_set() -> GraphInfo {
GraphInfo {
// sets do not get the default base set automatically
add_default_base_set: false,
..Default::default()
}
}
#[track_caller]
pub(crate) fn set_base_set(&mut self, set: BoxedSystemSet) {
if let Some(current) = &self.base_set {
panic!(
"Cannot set the base set because base set {current:?} has already been configured."
);
} else {
self.base_set = Some(set);
}
}
}
/// 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);

View file

@ -23,7 +23,7 @@ mod tests {
use std::sync::atomic::{AtomicU32, Ordering};
pub use crate as bevy_ecs;
pub use crate::schedule::{IntoSystemConfig, IntoSystemSetConfig, Schedule, SystemSet};
pub use crate::schedule::{IntoSystemSetConfig, Schedule, SystemSet};
pub use crate::system::{Res, ResMut};
pub use crate::{prelude::World, system::Resource};
@ -75,7 +75,7 @@ mod tests {
world.init_resource::<SystemOrder>();
schedule.add_system(make_function_system(0));
schedule.add_systems(make_function_system(0));
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
@ -88,7 +88,7 @@ mod tests {
world.init_resource::<SystemOrder>();
schedule.add_system(make_exclusive_system(0));
schedule.add_systems(make_exclusive_system(0));
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
@ -108,7 +108,7 @@ mod tests {
for _ in 0..thread_count {
let inner = barrier.clone();
schedule.add_system(move || {
schedule.add_systems(move || {
inner.wait();
});
}
@ -195,6 +195,50 @@ mod tests {
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![0, 1, 2, 3]);
}
#[test]
fn add_systems_correct_order_nested() {
let mut world = World::new();
let mut schedule = Schedule::new();
world.init_resource::<SystemOrder>();
schedule.add_systems(
(
(make_function_system(0), make_function_system(1)).chain(),
make_function_system(2),
(make_function_system(3), make_function_system(4)).chain(),
(
make_function_system(5),
(make_function_system(6), make_function_system(7)),
),
(
(make_function_system(8), make_function_system(9)).chain(),
make_function_system(10),
),
)
.chain(),
);
schedule.run(&mut world);
let order = &world.resource::<SystemOrder>().0;
assert_eq!(
&order[0..5],
&[0, 1, 2, 3, 4],
"first five items should be exactly ordered"
);
let unordered = &order[5..8];
assert!(
unordered.contains(&5) && unordered.contains(&6) && unordered.contains(&7),
"unordered must be 5, 6, and 7 in any order"
);
let partially_ordered = &order[8..11];
assert!(
partially_ordered == [8, 9, 10] || partially_ordered == [10, 8, 9],
"partially_ordered must be [8, 9, 10] or [10, 8, 9]"
);
assert!(order.len() == 11, "must have exacty 11 order entries");
}
}
mod conditions {
@ -210,7 +254,7 @@ mod tests {
world.init_resource::<RunConditionBool>();
world.init_resource::<SystemOrder>();
schedule.add_system(
schedule.add_systems(
make_function_system(0).run_if(|condition: Res<RunConditionBool>| condition.0),
);
@ -256,7 +300,7 @@ mod tests {
world.init_resource::<RunConditionBool>();
world.init_resource::<SystemOrder>();
schedule.add_system(
schedule.add_systems(
make_exclusive_system(0).run_if(|condition: Res<RunConditionBool>| condition.0),
);
@ -294,13 +338,13 @@ mod tests {
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.add_systems(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.add_systems(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.add_systems(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.add_systems(counting_system.in_set(TestSet::D));
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
@ -314,13 +358,13 @@ mod tests {
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.add_systems(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.add_systems(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.add_systems(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.add_systems(counting_system.in_set(TestSet::D).run_if(|| true));
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
@ -337,7 +381,7 @@ mod tests {
world.init_resource::<Bool2>();
let mut schedule = Schedule::default();
schedule.add_system(
schedule.add_systems(
counting_system
.run_if(|res1: Res<RunConditionBool>| res1.is_changed())
.run_if(|res2: Res<Bool2>| res2.is_changed()),
@ -391,7 +435,7 @@ mod tests {
.run_if(|res2: Res<Bool2>| res2.is_changed()),
);
schedule.add_system(counting_system.in_set(TestSet::A));
schedule.add_systems(counting_system.in_set(TestSet::A));
// both resource were just added.
schedule.run(&mut world);
@ -438,7 +482,7 @@ mod tests {
schedule
.configure_set(TestSet::A.run_if(|res1: Res<RunConditionBool>| res1.is_changed()));
schedule.add_system(
schedule.add_systems(
counting_system
.run_if(|res2: Res<Bool2>| res2.is_changed())
.in_set(TestSet::A),
@ -544,7 +588,7 @@ mod tests {
assert!(result.is_ok());
// Schedule another `foo`.
schedule.add_system(foo);
schedule.add_systems(foo);
// When there are multiple instances of `foo`, dependencies on
// `foo` are no longer allowed. Too much ambiguity.
@ -556,11 +600,11 @@ mod tests {
// same goes for `ambiguous_with`
let mut schedule = Schedule::new();
schedule.add_system(foo);
schedule.add_system(bar.ambiguous_with(foo));
schedule.add_systems(foo);
schedule.add_systems(bar.ambiguous_with(foo));
let result = schedule.initialize(&mut world);
assert!(result.is_ok());
schedule.add_system(foo);
schedule.add_systems(foo);
let result = schedule.initialize(&mut world);
assert!(matches!(
result,
@ -626,7 +670,7 @@ mod tests {
fn foo() {}
// Add `foo` to both `A` and `C`.
schedule.add_system(foo.in_set(TestSet::A).in_set(TestSet::C));
schedule.add_systems(foo.in_set(TestSet::A).in_set(TestSet::C));
// Order `A -> B -> C`.
schedule.configure_sets((
@ -664,140 +708,4 @@ mod tests {
assert!(matches!(result, Err(ScheduleBuildError::Ambiguity)));
}
}
mod base_sets {
use super::*;
#[derive(SystemSet, Hash, Debug, Eq, PartialEq, Clone)]
#[system_set(base)]
enum Base {
A,
B,
}
#[derive(SystemSet, Hash, Debug, Eq, PartialEq, Clone)]
enum Normal {
X,
Y,
}
#[test]
#[should_panic]
fn disallow_adding_base_sets_to_sets() {
let mut schedule = Schedule::new();
schedule.configure_set(Base::A.in_set(Normal::X));
}
#[test]
#[should_panic]
fn disallow_adding_base_sets_to_base_sets() {
let mut schedule = Schedule::new();
schedule.configure_set(Base::A.in_base_set(Base::B));
}
#[test]
#[should_panic]
fn disallow_adding_set_to_multiple_base_sets() {
let mut schedule = Schedule::new();
schedule.configure_set(Normal::X.in_base_set(Base::A).in_base_set(Base::B));
}
#[test]
#[should_panic]
fn disallow_adding_sets_to_multiple_base_sets() {
let mut schedule = Schedule::new();
schedule.configure_sets(
(Normal::X, Normal::Y)
.in_base_set(Base::A)
.in_base_set(Base::B),
);
}
#[test]
#[should_panic]
fn disallow_adding_system_to_multiple_base_sets() {
let mut schedule = Schedule::new();
schedule.add_system(named_system.in_base_set(Base::A).in_base_set(Base::B));
}
#[test]
#[should_panic]
fn disallow_adding_systems_to_multiple_base_sets() {
let mut schedule = Schedule::new();
schedule.add_systems(
(make_function_system(0), make_function_system(1))
.in_base_set(Base::A)
.in_base_set(Base::B),
);
}
#[test]
fn disallow_multiple_base_sets() {
let mut world = World::new();
let mut schedule = Schedule::new();
schedule
.configure_set(Normal::X.in_base_set(Base::A))
.configure_set(Normal::Y.in_base_set(Base::B))
.add_system(named_system.in_set(Normal::X).in_set(Normal::Y));
let result = schedule.initialize(&mut world);
assert!(matches!(
result,
Err(ScheduleBuildError::SystemInMultipleBaseSets { .. })
));
let mut schedule = Schedule::new();
schedule
.configure_set(Normal::X.in_base_set(Base::A))
.configure_set(Normal::Y.in_base_set(Base::B).in_set(Normal::X));
let result = schedule.initialize(&mut world);
assert!(matches!(
result,
Err(ScheduleBuildError::SetInMultipleBaseSets { .. })
));
}
#[test]
fn allow_same_base_sets() {
let mut world = World::new();
let mut schedule = Schedule::new();
schedule
.configure_set(Normal::X.in_base_set(Base::A))
.configure_set(Normal::Y.in_base_set(Base::A))
.add_system(named_system.in_set(Normal::X).in_set(Normal::Y));
let result = schedule.initialize(&mut world);
assert!(matches!(result, Ok(())));
let mut schedule = Schedule::new();
schedule
.configure_set(Normal::X.in_base_set(Base::A))
.configure_set(Normal::Y.in_base_set(Base::A).in_set(Normal::X));
let result = schedule.initialize(&mut world);
assert!(matches!(result, Ok(())));
}
#[test]
fn default_base_set_ordering() {
let mut world = World::default();
let mut schedule = Schedule::default();
world.init_resource::<SystemOrder>();
schedule
.set_default_base_set(Base::A)
.configure_set(Base::A.before(Base::B))
.add_systems((
make_function_system(0).in_base_set(Base::B),
make_function_system(1),
));
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![1, 0]);
}
}
}

View file

@ -43,17 +43,11 @@ impl Schedules {
/// and the old schedule is returned. Otherwise, `None` is returned.
pub fn insert(&mut self, label: impl ScheduleLabel, schedule: Schedule) -> Option<Schedule> {
let label = label.dyn_clone();
if self.inner.contains_key(&label) {
warn!("schedule with label {:?} already exists", label);
}
self.inner.insert(label, schedule)
}
/// Removes the schedule corresponding to the `label` from the map, returning it if it existed.
pub fn remove(&mut self, label: &dyn ScheduleLabel) -> Option<Schedule> {
if !self.inner.contains_key(label) {
warn!("schedule with label {:?} not found", label);
}
self.inner.remove(label)
}
@ -62,9 +56,6 @@ impl Schedules {
&mut self,
label: &dyn ScheduleLabel,
) -> Option<(Box<dyn ScheduleLabel>, Schedule)> {
if !self.inner.contains_key(label) {
warn!("schedule with label {:?} not found", label);
}
self.inner.remove_entry(label)
}
@ -134,7 +125,7 @@ fn make_executor(kind: ExecutorKind) -> Box<dyn SystemExecutor> {
/// fn main() {
/// let mut world = World::new();
/// let mut schedule = Schedule::default();
/// schedule.add_system(hello_world);
/// schedule.add_systems(hello_world);
///
/// schedule.run(&mut world);
/// }
@ -183,21 +174,16 @@ impl Schedule {
}
}
pub fn set_default_base_set(&mut self, default_base_set: impl SystemSet) -> &mut Self {
self.graph
.set_default_base_set(Some(Box::new(default_base_set)));
self
}
/// Add a system to the schedule.
pub fn add_system<M>(&mut self, system: impl IntoSystemConfig<M>) -> &mut Self {
self.graph.add_system(system);
#[deprecated(since = "0.11.0", note = "please use `add_systems` instead")]
pub fn add_system<M>(&mut self, system: impl IntoSystemConfigs<M>) -> &mut Self {
self.graph.add_systems_inner(system.into_configs(), false);
self
}
/// Add a collection of systems to the schedule.
pub fn add_systems<M>(&mut self, systems: impl IntoSystemConfigs<M>) -> &mut Self {
self.graph.add_systems(systems);
self.graph.add_systems_inner(systems.into_configs(), false);
self
}
@ -346,29 +332,14 @@ impl Dag {
}
}
/// Describes which base set (i.e. [`SystemSet`] where [`SystemSet::is_base`] returns true)
/// a system belongs to.
///
/// Note that this is only populated once [`ScheduleGraph::build_schedule`] is called.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum BaseSetMembership {
Uncalculated,
None,
Some(NodeId),
}
/// A [`SystemSet`] with metadata, stored in a [`ScheduleGraph`].
struct SystemSetNode {
inner: BoxedSystemSet,
base_set_membership: BaseSetMembership,
}
impl SystemSetNode {
pub fn new(set: BoxedSystemSet) -> Self {
Self {
inner: set,
base_set_membership: BaseSetMembership::Uncalculated,
}
Self { inner: set }
}
pub fn name(&self) -> String {
@ -383,14 +354,12 @@ impl SystemSetNode {
/// A [`BoxedSystem`] with metadata, stored in a [`ScheduleGraph`].
struct SystemNode {
inner: Option<BoxedSystem>,
base_set_membership: BaseSetMembership,
}
impl SystemNode {
pub fn new(system: BoxedSystem) -> Self {
Self {
inner: Some(system),
base_set_membership: BaseSetMembership::Uncalculated,
}
}
@ -401,10 +370,6 @@ impl SystemNode {
pub fn get_mut(&mut self) -> Option<&mut BoxedSystem> {
self.inner.as_mut()
}
pub fn name(&self) -> String {
format!("{:?}", &self.inner)
}
}
/// Metadata for a [`Schedule`].
@ -416,7 +381,6 @@ pub struct ScheduleGraph {
system_set_conditions: Vec<Option<Vec<BoxedCondition>>>,
system_set_ids: HashMap<BoxedSystemSet, NodeId>,
uninit: Vec<(NodeId, usize)>,
maybe_default_base_set: Vec<NodeId>,
hierarchy: Dag,
dependency: Dag,
dependency_flattened: Dag,
@ -426,7 +390,6 @@ pub struct ScheduleGraph {
conflicting_systems: Vec<(NodeId, NodeId, Vec<ComponentId>)>,
changed: bool,
settings: ScheduleBuildSettings,
default_base_set: Option<BoxedSystemSet>,
}
impl ScheduleGraph {
@ -437,7 +400,6 @@ impl ScheduleGraph {
system_sets: Vec::new(),
system_set_conditions: Vec::new(),
system_set_ids: HashMap::new(),
maybe_default_base_set: Vec::new(),
uninit: Vec::new(),
hierarchy: Dag::new(),
dependency: Dag::new(),
@ -448,7 +410,6 @@ impl ScheduleGraph {
conflicting_systems: Vec::new(),
changed: false,
settings: default(),
default_base_set: None,
}
}
@ -491,44 +452,29 @@ impl ScheduleGraph {
}
/// Returns an iterator over all systems in this schedule.
///
/// Note that the [`BaseSetMembership`] will only be initialized after [`ScheduleGraph::build_schedule`] is called.
pub fn systems(
&self,
) -> impl Iterator<
Item = (
NodeId,
&dyn System<In = (), Out = ()>,
BaseSetMembership,
&[BoxedCondition],
),
> {
) -> impl Iterator<Item = (NodeId, &dyn System<In = (), Out = ()>, &[BoxedCondition])> {
self.systems
.iter()
.zip(self.system_conditions.iter())
.enumerate()
.filter_map(|(i, (system_node, condition))| {
let system = system_node.inner.as_deref()?;
let base_set_membership = system_node.base_set_membership;
let condition = condition.as_ref()?.as_slice();
Some((NodeId::System(i), system, base_set_membership, condition))
Some((NodeId::System(i), system, condition))
})
}
/// Returns an iterator over all system sets in this schedule.
///
/// Note that the [`BaseSetMembership`] will only be initialized after [`ScheduleGraph::build_schedule`] is called.
pub fn system_sets(
&self,
) -> impl Iterator<Item = (NodeId, &dyn SystemSet, BaseSetMembership, &[BoxedCondition])> {
pub fn system_sets(&self) -> impl Iterator<Item = (NodeId, &dyn SystemSet, &[BoxedCondition])> {
self.system_set_ids.iter().map(|(_, node_id)| {
let set_node = &self.system_sets[node_id.index()];
let set = &*set_node.inner;
let base_set_membership = set_node.base_set_membership;
let conditions = self.system_set_conditions[node_id.index()]
.as_deref()
.unwrap_or(&[]);
(*node_id, set, base_set_membership, conditions)
(*node_id, set, conditions)
})
}
@ -556,47 +502,144 @@ impl ScheduleGraph {
&self.conflicting_systems
}
fn add_systems<M>(&mut self, systems: impl IntoSystemConfigs<M>) {
let SystemConfigs { systems, chained } = systems.into_configs();
let mut system_iter = systems.into_iter();
if chained {
let Some(prev) = system_iter.next() else { return };
let mut prev_id = self.add_system_inner(prev).unwrap();
for next in system_iter {
let next_id = self.add_system_inner(next).unwrap();
self.dependency.graph.add_edge(prev_id, next_id, ());
prev_id = next_id;
/// Adds the systems to the graph. Returns a vector of all node ids contained the nested `SystemConfigs`
/// if `ancestor_chained` is true. Also returns true if "densely chained", meaning that all nested items
/// are linearly chained in the order they are defined
fn add_systems_inner(
&mut self,
configs: SystemConfigs,
ancestor_chained: bool,
) -> AddSystemsInnerResult {
match configs {
SystemConfigs::SystemConfig(config) => {
let node_id = self.add_system_inner(config).unwrap();
if ancestor_chained {
AddSystemsInnerResult {
densely_chained: true,
nodes: vec![node_id],
}
} else {
AddSystemsInnerResult {
densely_chained: true,
nodes: Vec::new(),
}
}
}
} else {
for system in system_iter {
self.add_system_inner(system).unwrap();
SystemConfigs::Configs { configs, chained } => {
let mut config_iter = configs.into_iter();
let mut nodes_in_scope = Vec::new();
let mut densely_chained = true;
if chained {
let Some(prev) = config_iter.next() else {
return AddSystemsInnerResult {
nodes: Vec::new(),
densely_chained: true
}
};
let mut previous_result = self.add_systems_inner(prev, true);
densely_chained = previous_result.densely_chained;
for current in config_iter {
let current_result = self.add_systems_inner(current, true);
densely_chained = densely_chained && current_result.densely_chained;
match (
previous_result.densely_chained,
current_result.densely_chained,
) {
// Both groups are "densely" chained, so we can simplify the graph by only
// chaining the last in the previous list to the first in the current list
(true, true) => {
let last_in_prev = previous_result.nodes.last().unwrap();
let first_in_current = current_result.nodes.first().unwrap();
self.dependency.graph.add_edge(
*last_in_prev,
*first_in_current,
(),
);
}
// The previous group is "densely" chained, so we can simplify the graph by only
// chaining the last item from the previous list to every item in the current list
(true, false) => {
let last_in_prev = previous_result.nodes.last().unwrap();
for current_node in &current_result.nodes {
self.dependency.graph.add_edge(
*last_in_prev,
*current_node,
(),
);
}
}
// The current list is currently "densely" chained, so we can simplify the graph by
// only chaining every item in the previous list to the first item in the current list
(false, true) => {
let first_in_current = current_result.nodes.first().unwrap();
for previous_node in &previous_result.nodes {
self.dependency.graph.add_edge(
*previous_node,
*first_in_current,
(),
);
}
}
// Neither of the lists are "densely" chained, so we must chain every item in the first
// list to every item in the second list
(false, false) => {
for previous_node in &previous_result.nodes {
for current_node in &current_result.nodes {
self.dependency.graph.add_edge(
*previous_node,
*current_node,
(),
);
}
}
}
}
if ancestor_chained {
nodes_in_scope.append(&mut previous_result.nodes);
}
previous_result = current_result;
}
// ensure the last config's nodes are added
if ancestor_chained {
nodes_in_scope.append(&mut previous_result.nodes);
}
} else {
let more_than_one_entry = config_iter.len() > 1;
for config in config_iter {
let result = self.add_systems_inner(config, ancestor_chained);
densely_chained = densely_chained && result.densely_chained;
if ancestor_chained {
nodes_in_scope.extend(result.nodes);
}
}
// an "unchained" SystemConfig is only densely chained if it has exactly one densely chained entry
if more_than_one_entry {
densely_chained = false;
}
}
AddSystemsInnerResult {
nodes: nodes_in_scope,
densely_chained,
}
}
}
}
fn add_system<M>(&mut self, system: impl IntoSystemConfig<M>) {
self.add_system_inner(system).unwrap();
}
fn add_system_inner<M>(
&mut self,
system: impl IntoSystemConfig<M>,
) -> Result<NodeId, ScheduleBuildError> {
let SystemConfig {
system,
graph_info,
conditions,
} = system.into_config();
fn add_system_inner(&mut self, config: SystemConfig) -> Result<NodeId, ScheduleBuildError> {
let id = NodeId::System(self.systems.len());
// graph updates are immediate
self.update_graphs(id, graph_info, false)?;
self.update_graphs(id, config.graph_info)?;
// system init has to be deferred (need `&mut World`)
self.uninit.push((id, 0));
self.systems.push(SystemNode::new(system));
self.system_conditions.push(Some(conditions));
self.systems.push(SystemNode::new(config.system));
self.system_conditions.push(Some(config.conditions));
Ok(id)
}
@ -639,7 +682,7 @@ impl ScheduleGraph {
};
// graph updates are immediate
self.update_graphs(id, graph_info, set.is_base())?;
self.update_graphs(id, graph_info)?;
// system init has to be deferred (need `&mut World`)
let system_set_conditions =
@ -724,7 +767,6 @@ impl ScheduleGraph {
&mut self,
id: NodeId,
graph_info: GraphInfo,
is_base_set: bool,
) -> Result<(), ScheduleBuildError> {
self.check_sets(&id, &graph_info)?;
self.check_edges(&id, &graph_info)?;
@ -734,8 +776,6 @@ impl ScheduleGraph {
sets,
dependencies,
ambiguous_with,
base_set,
add_default_base_set,
..
} = graph_info;
@ -749,30 +789,6 @@ impl ScheduleGraph {
self.dependency.graph.add_node(set);
}
// If the current node is not a base set, set the base set if it was configured
if !is_base_set {
if let Some(base_set) = base_set {
let set_id = self.system_set_ids[&base_set];
self.hierarchy.graph.add_edge(set_id, id, ());
} else if let Some(default_base_set) = &self.default_base_set {
if add_default_base_set {
match id {
NodeId::System(_) => {
// Queue the default base set. We queue systems instead of adding directly to allow
// sets to define base sets, which will override the default inheritance behavior
self.maybe_default_base_set.push(id);
}
NodeId::Set(_) => {
// Sets should be added automatically because developers explicitly called
// in_default_base_set()
let set_id = self.system_set_ids[default_base_set];
self.hierarchy.graph.add_edge(set_id, id, ());
}
}
}
}
}
if !self.dependency.graph.contains_node(id) {
self.dependency.graph.add_node(id);
}
@ -832,149 +848,15 @@ impl ScheduleGraph {
}
}
/// Calculates the base set for each node and caches the results on the node
fn calculate_base_sets_and_detect_cycles(&mut self) -> Result<(), ScheduleBuildError> {
let set_ids = (0..self.system_sets.len()).map(NodeId::Set);
let system_ids = (0..self.systems.len()).map(NodeId::System);
let mut visited_sets = vec![false; self.system_sets.len()];
// reset base set membership, as this can change when the schedule updates
for system in &mut self.systems {
system.base_set_membership = BaseSetMembership::Uncalculated;
}
for system_set in &mut self.system_sets {
system_set.base_set_membership = BaseSetMembership::Uncalculated;
}
for node_id in set_ids.chain(system_ids) {
Self::calculate_base_set(
&self.hierarchy,
&mut self.system_sets,
&mut self.systems,
&mut visited_sets,
node_id,
)?;
}
Ok(())
}
fn calculate_base_set(
hierarchy: &Dag,
system_sets: &mut [SystemSetNode],
systems: &mut [SystemNode],
visited_sets: &mut [bool],
node_id: NodeId,
) -> Result<Option<NodeId>, ScheduleBuildError> {
let base_set_membership = match node_id {
// systems only have
NodeId::System(_) => BaseSetMembership::Uncalculated,
NodeId::Set(index) => {
let set_node = &mut system_sets[index];
if set_node.inner.is_base() {
set_node.base_set_membership = BaseSetMembership::Some(node_id);
}
set_node.base_set_membership
}
};
let base_set = match base_set_membership {
BaseSetMembership::None => None,
BaseSetMembership::Some(node_id) => Some(node_id),
BaseSetMembership::Uncalculated => {
let mut base_set: Option<NodeId> = None;
if let NodeId::Set(index) = node_id {
if visited_sets[index] {
return Err(ScheduleBuildError::HierarchyCycle);
}
visited_sets[index] = true;
}
for neighbor in hierarchy
.graph
.neighbors_directed(node_id, Direction::Incoming)
{
if let Some(calculated_base_set) = Self::calculate_base_set(
hierarchy,
system_sets,
systems,
visited_sets,
neighbor,
)? {
if let Some(first_set) = base_set {
if first_set != calculated_base_set {
return Err(match node_id {
NodeId::System(index) => {
ScheduleBuildError::SystemInMultipleBaseSets {
system: systems[index].name(),
first_set: system_sets[first_set.index()].name(),
second_set: system_sets[calculated_base_set.index()]
.name(),
}
}
NodeId::Set(index) => {
ScheduleBuildError::SetInMultipleBaseSets {
set: system_sets[index].name(),
first_set: system_sets[first_set.index()].name(),
second_set: system_sets[calculated_base_set.index()]
.name(),
}
}
});
}
}
base_set = Some(calculated_base_set);
}
}
match node_id {
NodeId::System(index) => {
systems[index].base_set_membership = if let Some(base_set) = base_set {
BaseSetMembership::Some(base_set)
} else {
BaseSetMembership::None
};
}
NodeId::Set(index) => {
system_sets[index].base_set_membership = if let Some(base_set) = base_set {
BaseSetMembership::Some(base_set)
} else {
BaseSetMembership::None
};
}
}
base_set
}
};
Ok(base_set)
}
/// Build a [`SystemSchedule`] optimized for scheduler access from the [`ScheduleGraph`].
///
/// This method also
/// - calculates [`BaseSetMembership`]
/// - checks for dependency or hierarchy cycles
/// - checks for system access conflicts and reports ambiguities
pub fn build_schedule(
&mut self,
components: &Components,
) -> Result<SystemSchedule, ScheduleBuildError> {
self.calculate_base_sets_and_detect_cycles()?;
// Add missing base set membership to systems that defaulted to using the
// default base set and weren't added to a set that belongs to a base set.
if let Some(default_base_set) = &self.default_base_set {
let default_set_id = self.system_set_ids[default_base_set];
for system_id in std::mem::take(&mut self.maybe_default_base_set) {
let system_node = &mut self.systems[system_id.index()];
if system_node.base_set_membership == BaseSetMembership::None {
self.hierarchy.graph.add_edge(default_set_id, system_id, ());
system_node.base_set_membership = BaseSetMembership::Some(default_set_id);
}
debug_assert_ne!(
system_node.base_set_membership,
BaseSetMembership::Uncalculated,
"base set membership should have been calculated"
);
}
}
// check hierarchy for cycles
self.hierarchy.topsort = self
.topsort_graph(&self.hierarchy.graph, ReportCycles::Hierarchy)
@ -1340,17 +1222,14 @@ impl ScheduleGraph {
Ok(())
}
}
fn set_default_base_set(&mut self, set: Option<BoxedSystemSet>) {
if let Some(set) = set {
self.default_base_set = Some(set.dyn_clone());
if self.system_set_ids.get(&set).is_none() {
self.add_set(set);
}
} else {
self.default_base_set = None;
}
}
/// Values returned by `ScheduleGraph::add_systems_inner`
struct AddSystemsInnerResult {
/// All nodes contained inside this add_systems_inner call's SystemConfigs hierarchy
nodes: Vec<NodeId>,
/// True if and only if all nodes are "densely chained"
densely_chained: bool,
}
/// Used to select the appropriate reporting function.

View file

@ -23,34 +23,10 @@ pub trait SystemSet: DynHash + Debug + Send + Sync + 'static {
None
}
/// Returns `true` if this set is a "base system set". Systems
/// can only belong to one base set at a time. Systems and Sets
/// can only be added to base sets using specialized `in_base_set`
/// APIs. This enables "mutually exclusive" behaviors. It also
/// enables schedules to have a "default base set", which can be used
/// to apply default configuration to systems.
fn is_base(&self) -> bool {
false
}
/// Creates a boxed clone of the label corresponding to this system set.
fn dyn_clone(&self) -> Box<dyn SystemSet>;
}
/// A marker trait for `SystemSet` types where [`is_base`] returns `true`.
/// This should only be implemented for types that satisfy this requirement.
/// It is automatically implemented for base set types by `#[derive(SystemSet)]`.
///
/// [`is_base`]: SystemSet::is_base
pub trait BaseSystemSet: SystemSet {}
/// A marker trait for `SystemSet` types where [`is_base`] returns `false`.
/// This should only be implemented for types that satisfy this requirement.
/// It is automatically implemented for non-base set types by `#[derive(SystemSet)]`.
///
/// [`is_base`]: SystemSet::is_base
pub trait FreeSystemSet: SystemSet {}
impl PartialEq for dyn SystemSet {
fn eq(&self, other: &Self) -> bool {
self.dyn_eq(other.as_dyn_eq())

View file

@ -50,7 +50,7 @@ use super::{ReadOnlySystem, System};
/// # world.init_resource::<RanFlag>();
/// #
/// # let mut app = Schedule::new();
/// app.add_system(my_system.run_if(Xor::new(
/// app.add_systems(my_system.run_if(Xor::new(
/// IntoSystem::into_system(resource_equals(A(1))),
/// IntoSystem::into_system(resource_equals(B(1))),
/// // The name of the combined system.

View file

@ -582,9 +582,9 @@ impl<'w, 's> Commands<'w, 's> {
/// # world.init_resource::<Counter>();
/// #
/// # let mut setup_schedule = Schedule::new();
/// # setup_schedule.add_system(setup);
/// # setup_schedule.add_systems(setup);
/// # let mut assert_schedule = Schedule::new();
/// # assert_schedule.add_system(assert_names);
/// # assert_schedule.add_systems(assert_names);
/// #
/// # setup_schedule.run(&mut world);
/// # assert_schedule.run(&mut world);

View file

@ -64,7 +64,7 @@
//!
//! // Configure this system to run in between the other two systems
//! // using explicit dependencies.
//! schedule.add_system(print_mid.after(print_first).before(print_last));
//! schedule.add_systems(print_mid.after(print_first).before(print_last));
//! // Prints "Hello, World!"
//! schedule.run(&mut world);
//!
@ -214,7 +214,7 @@ mod tests {
fn run_system<Marker, S: IntoSystem<(), (), Marker>>(world: &mut World, system: S) {
let mut schedule = Schedule::default();
schedule.add_system(system);
schedule.add_systems(system);
schedule.run(world);
}

View file

@ -17,7 +17,7 @@ use std::borrow::Cow;
///
/// Systems are executed in parallel, in opportunistic order; data access is managed automatically.
/// It's possible to specify explicit execution order between specific systems,
/// see [`IntoSystemConfig`](crate::schedule::IntoSystemConfig).
/// see [`IntoSystemConfigs`](crate::schedule::IntoSystemConfigs).
pub trait System: Send + Sync + 'static {
/// The system's input. See [`In`](crate::system::In) for
/// [`FunctionSystem`](crate::system::FunctionSystem)s.

View file

@ -674,7 +674,7 @@ unsafe impl SystemParam for &'_ World {
/// move |mut val| val.0 = value.0
/// }
///
/// // .add_system(reset_to_system(my_config))
/// // .add_systems(reset_to_system(my_config))
/// # assert_is_system(reset_to_system(Config(10)));
/// ```
pub struct Local<'s, T: FromWorld + Send + 'static>(pub(crate) &'s mut T);

View file

@ -138,7 +138,7 @@ pub mod adapter {
///
/// // Building a new schedule/app...
/// let mut sched = Schedule::default();
/// sched.add_system(
/// sched.add_systems(
/// // Panic if the load system returns an error.
/// load_save_system.pipe(system_adapter::unwrap)
/// )
@ -169,7 +169,7 @@ pub mod adapter {
///
/// // Building a new schedule/app...
/// let mut sched = Schedule::default();
/// sched.add_system(
/// sched.add_systems(
/// // Prints system information.
/// data_pipe_system.pipe(system_adapter::info)
/// )
@ -196,7 +196,7 @@ pub mod adapter {
///
/// // Building a new schedule/app...
/// let mut sched = Schedule::default();
/// sched.add_system(
/// sched.add_systems(
/// // Prints debug data from system.
/// parse_message_system.pipe(system_adapter::dbg)
/// )
@ -223,7 +223,7 @@ pub mod adapter {
///
/// // Building a new schedule/app...
/// # let mut sched = Schedule::default();
/// sched.add_system(
/// sched.add_systems(
/// // Prints system warning if system returns an error.
/// warning_pipe_system.pipe(system_adapter::warn)
/// )
@ -251,7 +251,7 @@ pub mod adapter {
/// use bevy_ecs::prelude::*;
/// // Building a new schedule/app...
/// let mut sched = Schedule::default();
/// sched.add_system(
/// sched.add_systems(
/// // Prints system error if system fails.
/// parse_error_message_system.pipe(system_adapter::error)
/// )
@ -287,7 +287,7 @@ pub mod adapter {
///
/// // Building a new schedule/app...
/// # let mut sched = Schedule::default(); sched
/// .add_system(
/// .add_systems(
/// // If the system fails, just move on and try again next frame.
/// fallible_system.pipe(system_adapter::ignore)
/// )

View file

@ -1,7 +1,7 @@
mod converter;
mod gilrs_system;
use bevy_app::{App, CoreSet, Plugin, StartupSet};
use bevy_app::{App, Plugin, PreStartup, PreUpdate};
use bevy_ecs::prelude::*;
use bevy_input::InputSystem;
use bevy_utils::tracing::error;
@ -20,14 +20,8 @@ impl Plugin for GilrsPlugin {
{
Ok(gilrs) => {
app.insert_non_send_resource(gilrs)
.add_startup_system(
gilrs_event_startup_system.in_base_set(StartupSet::PreStartup),
)
.add_system(
gilrs_event_system
.before(InputSystem)
.in_base_set(CoreSet::PreUpdate),
);
.add_systems(PreStartup, gilrs_event_startup_system)
.add_systems(PreUpdate, gilrs_event_system.before(InputSystem));
}
Err(err) => error!("Failed to start Gilrs. {}", err),
}

View file

@ -1,6 +1,6 @@
use std::marker::PhantomData;
use bevy_app::{App, CoreSet, Plugin};
use bevy_app::{App, Last, Plugin};
use bevy_core::Name;
use bevy_ecs::prelude::*;
use bevy_log::warn;
@ -96,10 +96,10 @@ impl<T: Component> Default for ValidParentCheckPlugin<T> {
impl<T: Component> Plugin for ValidParentCheckPlugin<T> {
fn build(&self, app: &mut App) {
app.init_resource::<ReportHierarchyIssue<T>>().add_system(
app.init_resource::<ReportHierarchyIssue<T>>().add_systems(
Last,
check_hierarchy_component_has_valid_parent::<T>
.run_if(resource_equals(ReportHierarchyIssue::<T>::new(true)))
.in_base_set(CoreSet::Last),
.run_if(resource_equals(ReportHierarchyIssue::<T>::new(true))),
);
}
}

View file

@ -11,7 +11,7 @@ use std::hash::Hash;
/// fn main() {
/// App::new()
/// .add_plugins(DefaultPlugins)
/// .add_system(pause_menu.run_if(input_toggle_active(false, KeyCode::Escape)))
/// .add_systems(Update, pause_menu.run_if(input_toggle_active(false, KeyCode::Escape)))
/// .run();
/// }
///
@ -33,7 +33,7 @@ use std::hash::Hash;
/// App::new()
/// .add_plugins(DefaultPlugins)
/// .init_resource::<Paused>()
/// .add_system(pause_menu.run_if(|paused: Res<Paused>| paused.0))
/// .add_systems(Update, pause_menu.run_if(|paused: Res<Paused>| paused.0))
/// .run();
/// }
///
@ -75,7 +75,7 @@ where
/// fn main() {
/// App::new()
/// .add_plugins(DefaultPlugins)
/// .add_system(jump.run_if(input_just_pressed(KeyCode::Space)))
/// .add_systems(Update, jump.run_if(input_just_pressed(KeyCode::Space)))
/// .run();
/// }
///

View file

@ -53,18 +53,18 @@ pub struct InputSystem;
impl Plugin for InputPlugin {
fn build(&self, app: &mut App) {
app.configure_set(InputSystem.in_base_set(CoreSet::PreUpdate))
app
// keyboard
.add_event::<KeyboardInput>()
.init_resource::<Input<KeyCode>>()
.init_resource::<Input<ScanCode>>()
.add_system(keyboard_input_system.in_set(InputSystem))
.add_systems(PreUpdate, keyboard_input_system.in_set(InputSystem))
// mouse
.add_event::<MouseButtonInput>()
.add_event::<MouseMotion>()
.add_event::<MouseWheel>()
.init_resource::<Input<MouseButton>>()
.add_system(mouse_button_input_system.in_set(InputSystem))
.add_systems(PreUpdate, mouse_button_input_system.in_set(InputSystem))
// gamepad
.add_event::<GamepadConnectionEvent>()
.add_event::<GamepadButtonChangedEvent>()
@ -76,6 +76,7 @@ impl Plugin for InputPlugin {
.init_resource::<Axis<GamepadAxis>>()
.init_resource::<Axis<GamepadButton>>()
.add_systems(
PreUpdate,
(
gamepad_event_system,
gamepad_connection_system.after(gamepad_event_system),
@ -91,7 +92,7 @@ impl Plugin for InputPlugin {
// touch
.add_event::<TouchInput>()
.init_resource::<Touches>()
.add_system(touch_screen_input_system.in_set(InputSystem));
.add_systems(PreUpdate, touch_screen_input_system.in_set(InputSystem));
// Register common types
app.register_type::<ButtonState>();

View file

@ -55,7 +55,7 @@ use bevy_render::{
render_phase::sort_phase_system,
render_resource::Shader,
view::{ViewSet, VisibilitySystems},
ExtractSchedule, RenderApp, RenderSet,
ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_transform::TransformSystem;
use environment_map::EnvironmentMapPlugin;
@ -176,59 +176,58 @@ impl Plugin for PbrPlugin {
.init_resource::<PointLightShadowMap>()
.add_plugin(ExtractResourcePlugin::<AmbientLight>::default())
.configure_sets(
PostUpdate,
(
SimulationLightSystems::AddClusters,
SimulationLightSystems::AddClustersFlush
.after(SimulationLightSystems::AddClusters)
.before(SimulationLightSystems::AssignLightsToClusters),
SimulationLightSystems::AddClustersFlush,
SimulationLightSystems::AssignLightsToClusters,
SimulationLightSystems::CheckLightVisibility,
SimulationLightSystems::UpdateDirectionalLightCascades,
SimulationLightSystems::UpdateLightFrusta,
)
.in_base_set(CoreSet::PostUpdate),
.chain(),
)
.add_plugin(FogPlugin)
.add_systems((
add_clusters.in_set(SimulationLightSystems::AddClusters),
apply_system_buffers.in_set(SimulationLightSystems::AddClustersFlush),
assign_lights_to_clusters
.in_set(SimulationLightSystems::AssignLightsToClusters)
.after(TransformSystem::TransformPropagate)
.after(VisibilitySystems::CheckVisibility)
.after(CameraUpdateSystem),
update_directional_light_cascades
.in_set(SimulationLightSystems::UpdateDirectionalLightCascades)
.after(TransformSystem::TransformPropagate)
.after(CameraUpdateSystem),
update_directional_light_frusta
.in_set(SimulationLightSystems::UpdateLightFrusta)
// This must run after CheckVisibility because it relies on ComputedVisibility::is_visible()
.after(VisibilitySystems::CheckVisibility)
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::UpdateDirectionalLightCascades)
// We assume that no entity will be both a directional light and a spot light,
// so these systems will run independently of one another.
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(update_spot_light_frusta),
update_point_light_frusta
.in_set(SimulationLightSystems::UpdateLightFrusta)
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::AssignLightsToClusters),
update_spot_light_frusta
.in_set(SimulationLightSystems::UpdateLightFrusta)
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::AssignLightsToClusters),
check_light_mesh_visibility
.in_set(SimulationLightSystems::CheckLightVisibility)
.after(VisibilitySystems::CalculateBoundsFlush)
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::UpdateLightFrusta)
// NOTE: This MUST be scheduled AFTER the core renderer visibility check
// because that resets entity ComputedVisibility for the first view
// which would override any results from this otherwise
.after(VisibilitySystems::CheckVisibility),
));
.add_systems(
PostUpdate,
(
add_clusters.in_set(SimulationLightSystems::AddClusters),
apply_system_buffers.in_set(SimulationLightSystems::AddClustersFlush),
assign_lights_to_clusters
.in_set(SimulationLightSystems::AssignLightsToClusters)
.after(TransformSystem::TransformPropagate)
.after(VisibilitySystems::CheckVisibility)
.after(CameraUpdateSystem),
update_directional_light_cascades
.in_set(SimulationLightSystems::UpdateDirectionalLightCascades)
.after(TransformSystem::TransformPropagate)
.after(CameraUpdateSystem),
update_directional_light_frusta
.in_set(SimulationLightSystems::UpdateLightFrusta)
// This must run after CheckVisibility because it relies on ComputedVisibility::is_visible()
.after(VisibilitySystems::CheckVisibility)
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::UpdateDirectionalLightCascades)
// We assume that no entity will be both a directional light and a spot light,
// so these systems will run independently of one another.
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(update_spot_light_frusta),
update_point_light_frusta
.in_set(SimulationLightSystems::UpdateLightFrusta)
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::AssignLightsToClusters),
update_spot_light_frusta
.in_set(SimulationLightSystems::UpdateLightFrusta)
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::AssignLightsToClusters),
check_light_mesh_visibility
.in_set(SimulationLightSystems::CheckLightVisibility)
.after(VisibilitySystems::CalculateBoundsFlush)
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::UpdateLightFrusta)
// NOTE: This MUST be scheduled AFTER the core renderer visibility check
// because that resets entity ComputedVisibility for the first view
// which would override any results from this otherwise
.after(VisibilitySystems::CheckVisibility),
),
);
app.world
.resource_mut::<Assets<StandardMaterial>>()
@ -248,31 +247,39 @@ impl Plugin for PbrPlugin {
// Extract the required data from the main world
render_app
.configure_set(RenderLightSystems::PrepareLights.in_set(RenderSet::Prepare))
.configure_set(RenderLightSystems::PrepareClusters.in_set(RenderSet::Prepare))
.configure_set(RenderLightSystems::QueueShadows.in_set(RenderSet::Queue))
.configure_sets(
Render,
(
RenderLightSystems::PrepareLights.in_set(RenderSet::Prepare),
RenderLightSystems::PrepareClusters.in_set(RenderSet::Prepare),
RenderLightSystems::QueueShadows.in_set(RenderSet::Queue),
),
)
.add_systems(
ExtractSchedule,
(
render::extract_clusters.in_set(RenderLightSystems::ExtractClusters),
render::extract_lights.in_set(RenderLightSystems::ExtractLights),
)
.in_schedule(ExtractSchedule),
),
)
.add_systems(
Render,
(
render::prepare_lights
.before(ViewSet::PrepareUniforms)
.in_set(RenderLightSystems::PrepareLights),
// A sync is needed after prepare_lights, before prepare_view_uniforms,
// because prepare_lights creates new views for shadow mapping
apply_system_buffers
.in_set(RenderSet::Prepare)
.after(RenderLightSystems::PrepareLights)
.before(ViewSet::PrepareUniforms),
render::prepare_clusters
.after(render::prepare_lights)
.in_set(RenderLightSystems::PrepareClusters),
sort_phase_system::<Shadow>.in_set(RenderSet::PhaseSort),
),
)
.add_systems((
render::prepare_lights
.before(ViewSet::PrepareUniforms)
.in_set(RenderLightSystems::PrepareLights),
// A sync is needed after prepare_lights, before prepare_view_uniforms,
// because prepare_lights creates new views for shadow mapping
apply_system_buffers
.in_set(RenderSet::Prepare)
.after(RenderLightSystems::PrepareLights)
.before(ViewSet::PrepareUniforms),
render::prepare_clusters
.after(render::prepare_lights)
.in_set(RenderLightSystems::PrepareClusters),
sort_phase_system::<Shadow>.in_set(RenderSet::PhaseSort),
))
.init_resource::<ShadowSamplers>()
.init_resource::<LightMeta>()
.init_resource::<GlobalLightMeta>();

View file

@ -3,7 +3,7 @@ use crate::{
MeshUniform, PrepassPipelinePlugin, PrepassPlugin, RenderLightSystems, SetMeshBindGroup,
SetMeshViewBindGroup, Shadow,
};
use bevy_app::{App, IntoSystemAppConfig, Plugin};
use bevy_app::{App, Plugin};
use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle};
use bevy_core_pipeline::{
core_3d::{AlphaMask3d, Opaque3d, Transparent3d},
@ -35,7 +35,7 @@ use bevy_render::{
renderer::RenderDevice,
texture::FallbackImage,
view::{ExtractedView, Msaa, VisibleEntities},
Extract, ExtractSchedule, RenderApp, RenderSet,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_utils::{tracing::error, HashMap, HashSet};
use std::hash::Hash;
@ -199,14 +199,17 @@ where
.init_resource::<ExtractedMaterials<M>>()
.init_resource::<RenderMaterials<M>>()
.init_resource::<SpecializedMeshPipelines<MaterialPipeline<M>>>()
.add_systems((
extract_materials::<M>.in_schedule(ExtractSchedule),
prepare_materials::<M>
.in_set(RenderSet::Prepare)
.after(PrepareAssetSet::PreAssetPrepare),
render::queue_shadows::<M>.in_set(RenderLightSystems::QueueShadows),
queue_material_meshes::<M>.in_set(RenderSet::Queue),
));
.add_systems(ExtractSchedule, extract_materials::<M>)
.add_systems(
Render,
(
prepare_materials::<M>
.in_set(RenderSet::Prepare)
.after(PrepareAssetSet::PreAssetPrepare),
render::queue_shadows::<M>.in_set(RenderLightSystems::QueueShadows),
queue_material_meshes::<M>.in_set(RenderSet::Queue),
),
);
}
// PrepassPipelinePlugin is required for shadow mapping and the optional PrepassPlugin

View file

@ -1,4 +1,4 @@
use bevy_app::{IntoSystemAppConfig, Plugin};
use bevy_app::Plugin;
use bevy_asset::{load_internal_asset, AssetServer, Handle, HandleUntyped};
use bevy_core_pipeline::{
prelude::Camera3d,
@ -39,7 +39,7 @@ use bevy_render::{
renderer::RenderDevice,
texture::{FallbackImagesDepth, FallbackImagesMsaa, TextureCache},
view::{ExtractedView, Msaa, ViewUniform, ViewUniformOffset, ViewUniforms, VisibleEntities},
Extract, ExtractSchedule, RenderApp, RenderSet,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_utils::{tracing::error, HashMap};
@ -102,7 +102,10 @@ where
};
render_app
.add_system(queue_prepass_view_bind_group::<M>.in_set(RenderSet::Queue))
.add_systems(
Render,
queue_prepass_view_bind_group::<M>.in_set(RenderSet::Queue),
)
.init_resource::<PrepassPipeline<M>>()
.init_resource::<PrepassViewBindGroup>()
.init_resource::<SpecializedMeshPipelines<PrepassPipeline<M>>>();
@ -130,15 +133,18 @@ where
};
render_app
.add_systems((
extract_camera_prepass_phase.in_schedule(ExtractSchedule),
prepare_prepass_textures
.in_set(RenderSet::Prepare)
.after(bevy_render::view::prepare_windows),
queue_prepass_material_meshes::<M>.in_set(RenderSet::Queue),
sort_phase_system::<Opaque3dPrepass>.in_set(RenderSet::PhaseSort),
sort_phase_system::<AlphaMask3dPrepass>.in_set(RenderSet::PhaseSort),
))
.add_systems(ExtractSchedule, extract_camera_prepass_phase)
.add_systems(
Render,
(
prepare_prepass_textures
.in_set(RenderSet::Prepare)
.after(bevy_render::view::prepare_windows),
queue_prepass_material_meshes::<M>.in_set(RenderSet::Queue),
sort_phase_system::<Opaque3dPrepass>.in_set(RenderSet::PhaseSort),
sort_phase_system::<AlphaMask3dPrepass>.in_set(RenderSet::PhaseSort),
),
)
.init_resource::<DrawFunctions<Opaque3dPrepass>>()
.init_resource::<DrawFunctions<AlphaMask3dPrepass>>()
.add_render_command::<Opaque3dPrepass, DrawPrepass<M>>()

View file

@ -8,7 +8,7 @@ use bevy_render::{
render_resource::{DynamicUniformBuffer, Shader, ShaderType},
renderer::{RenderDevice, RenderQueue},
view::ExtractedView,
RenderApp, RenderSet,
Render, RenderApp, RenderSet,
};
use crate::{FogFalloff, FogSettings};
@ -142,8 +142,11 @@ impl Plugin for FogPlugin {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<FogMeta>()
.add_system(prepare_fog.in_set(RenderFogSystems::PrepareFog))
.configure_set(RenderFogSystems::PrepareFog.in_set(RenderSet::Prepare));
.add_systems(Render, prepare_fog.in_set(RenderFogSystems::PrepareFog))
.configure_set(
Render,
RenderFogSystems::PrepareFog.in_set(RenderSet::Prepare),
);
}
}
}

View file

@ -4,7 +4,7 @@ use crate::{
ViewClusterBindings, ViewFogUniformOffset, ViewLightsUniformOffset, ViewShadowBindings,
CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS,
};
use bevy_app::{IntoSystemAppConfigs, Plugin};
use bevy_app::Plugin;
use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped};
use bevy_core_pipeline::{
prepass::ViewPrepassTextures,
@ -36,7 +36,7 @@ use bevy_render::{
FallbackImagesMsaa, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
},
view::{ComputedVisibility, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms},
Extract, ExtractSchedule, RenderApp, RenderSet,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_transform::components::GlobalTransform;
use std::num::NonZeroU64;
@ -107,12 +107,15 @@ impl Plugin for MeshRenderPlugin {
render_app
.init_resource::<MeshPipeline>()
.init_resource::<SkinnedMeshUniform>()
.add_systems((extract_meshes, extract_skinned_meshes).in_schedule(ExtractSchedule))
.add_systems((
prepare_skinned_meshes.in_set(RenderSet::Prepare),
queue_mesh_bind_group.in_set(RenderSet::Queue),
queue_mesh_view_bind_groups.in_set(RenderSet::Queue),
));
.add_systems(ExtractSchedule, (extract_meshes, extract_skinned_meshes))
.add_systems(
Render,
(
prepare_skinned_meshes.in_set(RenderSet::Prepare),
queue_mesh_bind_group.in_set(RenderSet::Queue),
queue_mesh_view_bind_groups.in_set(RenderSet::Queue),
),
);
}
}
}

View file

@ -7,6 +7,7 @@ use bevy_ecs::{prelude::*, reflect::ReflectComponent};
use bevy_reflect::std_traits::ReflectDefault;
use bevy_reflect::{Reflect, TypeUuid};
use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin};
use bevy_render::Render;
use bevy_render::{
extract_resource::{ExtractResource, ExtractResourcePlugin},
mesh::{Mesh, MeshVertexBufferLayout},
@ -47,7 +48,7 @@ impl Plugin for WireframePlugin {
.add_render_command::<Opaque3d, DrawWireframes>()
.init_resource::<WireframePipeline>()
.init_resource::<SpecializedMeshPipelines<WireframePipeline>>()
.add_system(queue_wireframes.in_set(RenderSet::Queue));
.add_systems(Render, queue_wireframes.in_set(RenderSet::Queue));
}
}
}

View file

@ -492,7 +492,7 @@ impl NormalizedRenderTarget {
/// The system function is generic over the camera projection type, and only instances of
/// [`OrthographicProjection`] and [`PerspectiveProjection`] are automatically added to
/// the app, as well as the runtime-selected [`Projection`].
/// The system runs during [`CoreSet::PostUpdate`].
/// The system runs during [`PostUpdate`](bevy_app::PostUpdate).
///
/// ## World Resources
///
@ -502,7 +502,6 @@ impl NormalizedRenderTarget {
/// [`OrthographicProjection`]: crate::camera::OrthographicProjection
/// [`PerspectiveProjection`]: crate::camera::PerspectiveProjection
/// [`Projection`]: crate::camera::Projection
/// [`CoreSet::PostUpdate`]: bevy_app::CoreSet::PostUpdate
pub fn camera_system<T: CameraProjection + Component>(
mut window_resized_events: EventReader<WindowResized>,
mut window_created_events: EventReader<WindowCreated>,

View file

@ -7,9 +7,9 @@ pub use camera::*;
pub use camera_driver_node::*;
pub use projection::*;
use crate::{render_graph::RenderGraph, ExtractSchedule, RenderApp, RenderSet};
use bevy_app::{App, IntoSystemAppConfig, Plugin};
use bevy_ecs::schedule::IntoSystemConfig;
use crate::{render_graph::RenderGraph, ExtractSchedule, Render, RenderApp, RenderSet};
use bevy_app::{App, Plugin};
use bevy_ecs::schedule::IntoSystemConfigs;
#[derive(Default)]
pub struct CameraPlugin;
@ -29,8 +29,8 @@ impl Plugin for CameraPlugin {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<SortedCameras>()
.add_system(extract_cameras.in_schedule(ExtractSchedule))
.add_system(sort_cameras.in_set(RenderSet::Prepare));
.add_systems(ExtractSchedule, extract_cameras)
.add_systems(Render, sort_cameras.in_set(RenderSet::Prepare));
let camera_driver_node = CameraDriverNode::new(&mut render_app.world);
let mut render_graph = render_app.world.resource_mut::<RenderGraph>();
render_graph.add_node(crate::main_graph::node::CAMERA_DRIVER, camera_driver_node);

View file

@ -1,6 +1,6 @@
use std::marker::PhantomData;
use bevy_app::{App, CoreSchedule, CoreSet, IntoSystemAppConfig, Plugin, StartupSet};
use bevy_app::{App, Plugin, PostStartup, PostUpdate};
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
use bevy_math::{Mat4, Rect, Vec2};
use bevy_reflect::{
@ -27,25 +27,24 @@ pub struct CameraUpdateSystem;
impl<T: CameraProjection + Component + GetTypeRegistration> Plugin for CameraProjectionPlugin<T> {
fn build(&self, app: &mut App) {
app.register_type::<T>()
.edit_schedule(CoreSchedule::Startup, |schedule| {
schedule.configure_set(CameraUpdateSystem.in_base_set(StartupSet::PostStartup));
})
.configure_set(CameraUpdateSystem.in_base_set(CoreSet::PostUpdate))
.add_systems((
crate::camera::camera_system::<T>
.on_startup()
.in_set(CameraUpdateSystem)
// We assume that each camera will only have one projection,
// so we can ignore ambiguities with all other monomorphizations.
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(CameraUpdateSystem),
.add_systems(
PostStartup,
crate::camera::camera_system::<T>
.in_set(CameraUpdateSystem)
// We assume that each camera will only have one projection,
// so we can ignore ambiguities with all other monomorphizations.
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(CameraUpdateSystem),
));
)
.add_systems(
PostUpdate,
crate::camera::camera_system::<T>
.in_set(CameraUpdateSystem)
// We assume that each camera will only have one projection,
// so we can ignore ambiguities with all other monomorphizations.
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(CameraUpdateSystem),
);
}
}

View file

@ -2,9 +2,9 @@ use crate::{
render_resource::{encase::internal::WriteInto, DynamicUniformBuffer, ShaderType},
renderer::{RenderDevice, RenderQueue},
view::ComputedVisibility,
Extract, ExtractSchedule, RenderApp, RenderSet,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_app::{App, IntoSystemAppConfig, Plugin};
use bevy_app::{App, Plugin};
use bevy_asset::{Asset, Handle};
use bevy_ecs::{
component::Component,
@ -83,7 +83,10 @@ impl<C: Component + ShaderType + WriteInto + Clone> Plugin for UniformComponentP
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.insert_resource(ComponentUniforms::<C>::default())
.add_system(prepare_uniform_components::<C>.in_set(RenderSet::Prepare));
.add_systems(
Render,
prepare_uniform_components::<C>.in_set(RenderSet::Prepare),
);
}
}
}
@ -180,9 +183,9 @@ impl<C: ExtractComponent> Plugin for ExtractComponentPlugin<C> {
fn build(&self, app: &mut App) {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
if self.only_extract_visible {
render_app.add_system(extract_visible_components::<C>.in_schedule(ExtractSchedule));
render_app.add_systems(ExtractSchedule, extract_visible_components::<C>);
} else {
render_app.add_system(extract_components::<C>.in_schedule(ExtractSchedule));
render_app.add_systems(ExtractSchedule, extract_components::<C>);
}
}
}

View file

@ -1,6 +1,6 @@
use std::marker::PhantomData;
use bevy_app::{App, IntoSystemAppConfig, Plugin};
use bevy_app::{App, Plugin};
use bevy_ecs::prelude::*;
pub use bevy_render_macros::ExtractResource;
@ -32,7 +32,7 @@ impl<R: ExtractResource> Default for ExtractResourcePlugin<R> {
impl<R: ExtractResource> Plugin for ExtractResourcePlugin<R> {
fn build(&self, app: &mut App) {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.add_system(extract_resource::<R>.in_schedule(ExtractSchedule));
render_app.add_systems(ExtractSchedule, extract_resource::<R>);
}
}
}

View file

@ -3,9 +3,9 @@ use crate::{
prelude::Shader,
render_resource::{ShaderType, UniformBuffer},
renderer::{RenderDevice, RenderQueue},
Extract, ExtractSchedule, RenderApp, RenderSet,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_app::{App, IntoSystemAppConfigs, Plugin};
use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, HandleUntyped};
use bevy_core::FrameCount;
use bevy_ecs::prelude::*;
@ -26,8 +26,8 @@ impl Plugin for GlobalsPlugin {
render_app
.init_resource::<GlobalsBuffer>()
.init_resource::<Time>()
.add_systems((extract_frame_count, extract_time).in_schedule(ExtractSchedule))
.add_system(prepare_globals_buffer.in_set(RenderSet::Prepare));
.add_systems(ExtractSchedule, (extract_frame_count, extract_time))
.add_systems(Render, prepare_globals_buffer.in_set(RenderSet::Prepare));
}
}
}

View file

@ -51,7 +51,7 @@ use crate::{
settings::WgpuSettings,
view::{ViewPlugin, WindowRenderPlugin},
};
use bevy_app::{App, AppLabel, CoreSchedule, Plugin, SubApp};
use bevy_app::{App, AppLabel, Plugin, SubApp};
use bevy_asset::{AddAsset, AssetServer};
use bevy_ecs::{prelude::*, schedule::ScheduleLabel, system::SystemState};
use bevy_utils::tracing::debug;
@ -101,7 +101,11 @@ pub enum RenderSet {
CleanupFlush,
}
impl RenderSet {
/// The main render schedule.
#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone)]
pub struct Render;
impl Render {
/// Sets up the base structure of the rendering [`Schedule`].
///
/// The sets defined in this enum are configured to run in order,
@ -226,43 +230,38 @@ impl Plugin for RenderPlugin {
.insert_resource(render_adapter.clone())
.init_resource::<ScratchMainWorld>();
let pipeline_cache = PipelineCache::new(device.clone());
let asset_server = app.world.resource::<AssetServer>().clone();
let mut render_app = App::empty();
render_app.add_simple_outer_schedule();
let mut render_schedule = RenderSet::base_schedule();
render_app.main_schedule_label = Box::new(Render);
// Prepare the schedule which extracts data from the main world to the render world
render_app.edit_schedule(ExtractSchedule, |schedule| {
schedule
.set_apply_final_buffers(false)
.add_system(PipelineCache::extract_shaders);
});
// This set applies the commands from the extract stage while the render schedule
// is running in parallel with the main app.
render_schedule.add_system(apply_extract_commands.in_set(RenderSet::ExtractCommands));
render_schedule.add_system(
PipelineCache::process_pipeline_queue_system
.before(render_system)
.in_set(RenderSet::Render),
);
render_schedule.add_system(render_system.in_set(RenderSet::Render));
render_schedule.add_system(World::clear_entities.in_set(RenderSet::Cleanup));
let mut extract_schedule = Schedule::new();
extract_schedule.set_apply_final_buffers(false);
render_app
.add_schedule(CoreSchedule::Main, render_schedule)
.add_schedule(ExtractSchedule, extract_schedule)
.add_schedule(Render, Render::base_schedule())
.init_resource::<render_graph::RenderGraph>()
.insert_resource(RenderInstance(instance))
.insert_resource(PipelineCache::new(device.clone()))
.insert_resource(device)
.insert_resource(queue)
.insert_resource(render_adapter)
.insert_resource(adapter_info)
.insert_resource(pipeline_cache)
.insert_resource(asset_server);
.insert_resource(app.world.resource::<AssetServer>().clone())
.add_systems(ExtractSchedule, PipelineCache::extract_shaders)
.add_systems(
Render,
(
// This set applies the commands from the extract stage while the render schedule
// is running in parallel with the main app.
apply_extract_commands.in_set(RenderSet::ExtractCommands),
(
PipelineCache::process_pipeline_queue_system.before(render_system),
render_system,
)
.in_set(RenderSet::Render),
World::clear_entities.in_set(RenderSet::Cleanup),
),
);
let (sender, receiver) = bevy_time::create_time_channels();
app.insert_resource(receiver);

View file

@ -1,6 +1,6 @@
use async_channel::{Receiver, Sender};
use bevy_app::{App, AppLabel, CoreSchedule, Plugin, SubApp};
use bevy_app::{App, AppLabel, Main, Plugin, SubApp};
use bevy_ecs::{
schedule::MainThreadExecutor,
system::Resource,
@ -72,8 +72,7 @@ impl Plugin for PipelinedRenderingPlugin {
app.insert_resource(MainThreadExecutor::new());
let mut sub_app = App::empty();
sub_app.add_simple_outer_schedule();
sub_app.init_schedule(CoreSchedule::Main);
sub_app.init_schedule(Main);
app.insert_sub_app(RenderExtractApp, SubApp::new(sub_app, update_rendering));
}

View file

@ -1,5 +1,5 @@
use crate::{Extract, ExtractSchedule, RenderApp, RenderSet};
use bevy_app::{App, IntoSystemAppConfig, Plugin};
use crate::{Extract, ExtractSchedule, Render, RenderApp, RenderSet};
use bevy_app::{App, Plugin};
use bevy_asset::{Asset, AssetEvent, Assets, Handle};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
@ -80,7 +80,12 @@ impl<A: RenderAsset> Plugin for RenderAssetPlugin<A> {
fn build(&self, app: &mut App) {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<ExtractedAssets<A>>()
.init_resource::<RenderAssets<A>>()
.init_resource::<PrepareNextFrameAssets<A>>()
.add_systems(ExtractSchedule, extract_render_asset::<A>)
.configure_sets(
Render,
(
PrepareAssetSet::PreAssetPrepare,
PrepareAssetSet::AssetPrepare,
@ -89,13 +94,10 @@ impl<A: RenderAsset> Plugin for RenderAssetPlugin<A> {
.chain()
.in_set(RenderSet::Prepare),
)
.init_resource::<ExtractedAssets<A>>()
.init_resource::<RenderAssets<A>>()
.init_resource::<PrepareNextFrameAssets<A>>()
.add_systems((
extract_render_asset::<A>.in_schedule(ExtractSchedule),
.add_systems(
Render,
prepare_assets::<A>.in_set(self.prepare_asset_set.clone()),
));
);
}
}
}

View file

@ -33,7 +33,7 @@ pub use texture_cache::*;
use crate::{
render_asset::{PrepareAssetSet, RenderAssetPlugin},
renderer::RenderDevice,
RenderApp, RenderSet,
Render, RenderApp, RenderSet,
};
use bevy_app::{App, Plugin};
use bevy_asset::{AddAsset, Assets};
@ -115,7 +115,10 @@ impl Plugin for ImagePlugin {
.init_resource::<FallbackImageCubemap>()
.init_resource::<FallbackImageMsaaCache>()
.init_resource::<FallbackImageDepthCache>()
.add_system(update_texture_cache_system.in_set(RenderSet::Cleanup));
.add_systems(
Render,
update_texture_cache_system.in_set(RenderSet::Cleanup),
);
}
}
}

View file

@ -14,7 +14,7 @@ use crate::{
render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView},
renderer::{RenderDevice, RenderQueue},
texture::{BevyDefault, TextureCache},
RenderApp, RenderSet,
Render, RenderApp, RenderSet,
};
use bevy_app::{App, Plugin};
use bevy_ecs::prelude::*;
@ -55,14 +55,17 @@ impl Plugin for ViewPlugin {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<ViewUniforms>()
.configure_set(ViewSet::PrepareUniforms.in_set(RenderSet::Prepare))
.add_systems((
prepare_view_uniforms.in_set(ViewSet::PrepareUniforms),
prepare_view_targets
.after(WindowSystem::Prepare)
.in_set(RenderSet::Prepare)
.after(crate::render_asset::prepare_assets::<Image>),
));
.configure_set(Render, ViewSet::PrepareUniforms.in_set(RenderSet::Prepare))
.add_systems(
Render,
(
prepare_view_uniforms.in_set(ViewSet::PrepareUniforms),
prepare_view_targets
.after(WindowSystem::Prepare)
.in_set(RenderSet::Prepare)
.after(crate::render_asset::prepare_assets::<Image>),
),
);
}
}
}

View file

@ -2,7 +2,7 @@ mod render_layers;
pub use render_layers::*;
use bevy_app::{CoreSet, Plugin};
use bevy_app::{Plugin, PostUpdate};
use bevy_asset::{Assets, Handle};
use bevy_ecs::prelude::*;
use bevy_hierarchy::{Children, Parent};
@ -91,8 +91,8 @@ impl ComputedVisibility {
/// Whether this entity is visible to something this frame. This is true if and only if [`Self::is_visible_in_hierarchy`] and [`Self::is_visible_in_view`]
/// are true. This is the canonical method to call to determine if an entity should be drawn.
/// This value is updated in [`CoreSet::PostUpdate`] by the [`VisibilitySystems::CheckVisibility`] system set.
/// Reading it during [`CoreSet::Update`] will yield the value from the previous frame.
/// This value is updated in [`PostUpdate`] by the [`VisibilitySystems::CheckVisibility`] system set.
/// Reading it during [`Update`](bevy_app::Update) will yield the value from the previous frame.
#[inline]
pub fn is_visible(&self) -> bool {
self.flags.bits == ComputedVisibilityFlags::all().bits
@ -100,7 +100,7 @@ impl ComputedVisibility {
/// Whether this entity is visible in the entity hierarchy, which is determined by the [`Visibility`] component.
/// This takes into account "visibility inheritance". If any of this entity's ancestors (see [`Parent`]) are hidden, this entity
/// will be hidden as well. This value is updated in the [`VisibilitySystems::VisibilityPropagate`], which lives under the [`CoreSet::PostUpdate`] set.
/// will be hidden as well. This value is updated in the [`VisibilitySystems::VisibilityPropagate`], which lives in the [`PostUpdate`] schedule.
#[inline]
pub fn is_visible_in_hierarchy(&self) -> bool {
self.flags
@ -110,8 +110,8 @@ impl ComputedVisibility {
/// Whether this entity is visible in _any_ view (Cameras, Lights, etc). Each entity type (and view type) should choose how to set this
/// value. For cameras and drawn entities, this will take into account [`RenderLayers`].
///
/// This value is reset to `false` every frame in [`VisibilitySystems::VisibilityPropagate`] during [`CoreSet::PostUpdate`].
/// Each entity type then chooses how to set this field in the [`VisibilitySystems::CheckVisibility`] system set, under [`CoreSet::PostUpdate`].
/// This value is reset to `false` every frame in [`VisibilitySystems::VisibilityPropagate`] during [`PostUpdate`].
/// Each entity type then chooses how to set this field in the [`VisibilitySystems::CheckVisibility`] system set, in [`PostUpdate`].
/// Meshes might use frustum culling to decide if they are visible in a view.
/// Other entities might just set this to `true` every frame.
#[inline]
@ -210,52 +210,49 @@ impl Plugin for VisibilityPlugin {
fn build(&self, app: &mut bevy_app::App) {
use VisibilitySystems::*;
app.configure_set(CalculateBounds.in_base_set(CoreSet::PostUpdate))
app
// We add an AABB component in CalculateBounds, which must be ready on the same frame.
.add_system(apply_system_buffers.in_set(CalculateBoundsFlush))
.configure_set(
CalculateBoundsFlush
.after(CalculateBounds)
.in_base_set(CoreSet::PostUpdate),
.add_systems(
PostUpdate,
apply_system_buffers.in_set(CalculateBoundsFlush),
)
.configure_set(UpdateOrthographicFrusta.in_base_set(CoreSet::PostUpdate))
.configure_set(UpdatePerspectiveFrusta.in_base_set(CoreSet::PostUpdate))
.configure_set(UpdateProjectionFrusta.in_base_set(CoreSet::PostUpdate))
.configure_set(CheckVisibility.in_base_set(CoreSet::PostUpdate))
.configure_set(VisibilityPropagate.in_base_set(CoreSet::PostUpdate))
.add_systems((
calculate_bounds.in_set(CalculateBounds),
update_frusta::<OrthographicProjection>
.in_set(UpdateOrthographicFrusta)
.after(camera_system::<OrthographicProjection>)
.after(TransformSystem::TransformPropagate)
// We assume that no camera will have more than one projection component,
// so these systems will run independently of one another.
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(update_frusta::<PerspectiveProjection>)
.ambiguous_with(update_frusta::<Projection>),
update_frusta::<PerspectiveProjection>
.in_set(UpdatePerspectiveFrusta)
.after(camera_system::<PerspectiveProjection>)
.after(TransformSystem::TransformPropagate)
// We assume that no camera will have more than one projection component,
// so these systems will run independently of one another.
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(update_frusta::<Projection>),
update_frusta::<Projection>
.in_set(UpdateProjectionFrusta)
.after(camera_system::<Projection>)
.after(TransformSystem::TransformPropagate),
visibility_propagate_system.in_set(VisibilityPropagate),
check_visibility
.in_set(CheckVisibility)
.after(CalculateBoundsFlush)
.after(UpdateOrthographicFrusta)
.after(UpdatePerspectiveFrusta)
.after(UpdateProjectionFrusta)
.after(VisibilityPropagate)
.after(TransformSystem::TransformPropagate),
));
.configure_set(PostUpdate, CalculateBoundsFlush.after(CalculateBounds))
.add_systems(
PostUpdate,
(
calculate_bounds.in_set(CalculateBounds),
update_frusta::<OrthographicProjection>
.in_set(UpdateOrthographicFrusta)
.after(camera_system::<OrthographicProjection>)
.after(TransformSystem::TransformPropagate)
// We assume that no camera will have more than one projection component,
// so these systems will run independently of one another.
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(update_frusta::<PerspectiveProjection>)
.ambiguous_with(update_frusta::<Projection>),
update_frusta::<PerspectiveProjection>
.in_set(UpdatePerspectiveFrusta)
.after(camera_system::<PerspectiveProjection>)
.after(TransformSystem::TransformPropagate)
// We assume that no camera will have more than one projection component,
// so these systems will run independently of one another.
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(update_frusta::<Projection>),
update_frusta::<Projection>
.in_set(UpdateProjectionFrusta)
.after(camera_system::<Projection>)
.after(TransformSystem::TransformPropagate),
visibility_propagate_system.in_set(VisibilityPropagate),
check_visibility
.in_set(CheckVisibility)
.after(CalculateBoundsFlush)
.after(UpdateOrthographicFrusta)
.after(UpdatePerspectiveFrusta)
.after(UpdateProjectionFrusta)
.after(VisibilityPropagate)
.after(TransformSystem::TransformPropagate),
),
);
}
}
@ -457,7 +454,7 @@ mod test {
#[test]
fn visibility_propagation() {
let mut app = App::new();
app.add_system(visibility_propagate_system);
app.add_systems(Update, visibility_propagate_system);
let root1 = app
.world
@ -576,7 +573,7 @@ mod test {
#[test]
fn visibility_propagation_unconditional_visible() {
let mut app = App::new();
app.add_system(visibility_propagate_system);
app.add_systems(Update, visibility_propagate_system);
let root1 = app
.world

View file

@ -1,9 +1,9 @@
use crate::{
render_resource::TextureView,
renderer::{RenderAdapter, RenderDevice, RenderInstance},
Extract, ExtractSchedule, RenderApp, RenderSet,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_app::{App, IntoSystemAppConfig, Plugin};
use bevy_app::{App, Plugin};
use bevy_ecs::prelude::*;
use bevy_utils::{tracing::debug, HashMap, HashSet};
use bevy_window::{
@ -32,9 +32,9 @@ impl Plugin for WindowRenderPlugin {
.init_resource::<ExtractedWindows>()
.init_resource::<WindowSurfaces>()
.init_non_send_resource::<NonSendMarker>()
.add_system(extract_windows.in_schedule(ExtractSchedule))
.configure_set(WindowSystem::Prepare.in_set(RenderSet::Prepare))
.add_system(prepare_windows.in_set(WindowSystem::Prepare));
.add_systems(ExtractSchedule, extract_windows)
.configure_set(Render, WindowSystem::Prepare.in_set(RenderSet::Prepare))
.add_systems(Render, prepare_windows.in_set(WindowSystem::Prepare));
}
}
}

View file

@ -24,7 +24,6 @@ pub mod prelude {
use bevy_app::prelude::*;
use bevy_asset::AddAsset;
use bevy_ecs::prelude::*;
#[derive(Default)]
pub struct ScenePlugin;
@ -36,9 +35,9 @@ impl Plugin for ScenePlugin {
.add_asset::<Scene>()
.init_asset_loader::<SceneLoader>()
.init_resource::<SceneSpawner>()
.add_system(scene_spawner_system)
.add_systems(Update, scene_spawner_system)
// Systems `*_bundle_spawner` must run before `scene_spawner_system`
.add_system(scene_spawner.in_base_set(CoreSet::PreUpdate));
.add_systems(PreUpdate, scene_spawner);
}
}

View file

@ -34,7 +34,7 @@ use bevy_reflect::TypeUuid;
use bevy_render::{
render_phase::AddRenderCommand,
render_resource::{Shader, SpecializedRenderPipelines},
ExtractSchedule, RenderApp, RenderSet,
ExtractSchedule, Render, RenderApp, RenderSet,
};
#[derive(Default)]
@ -71,13 +71,14 @@ impl Plugin for SpritePlugin {
.init_resource::<SpriteAssetEvents>()
.add_render_command::<Transparent2d, DrawSprite>()
.add_systems(
ExtractSchedule,
(
extract_sprites.in_set(SpriteSystem::ExtractSprites),
extract_sprite_events,
)
.in_schedule(ExtractSchedule),
),
)
.add_system(
.add_systems(
Render,
queue_sprites
.in_set(RenderSet::Queue)
.ambiguous_with(queue_material2d_meshes::<ColorMaterial>),

View file

@ -1,4 +1,4 @@
use bevy_app::{App, IntoSystemAppConfig, Plugin};
use bevy_app::{App, Plugin};
use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle};
use bevy_core_pipeline::{
core_2d::Transparent2d,
@ -32,7 +32,7 @@ use bevy_render::{
renderer::RenderDevice,
texture::FallbackImage,
view::{ComputedVisibility, ExtractedView, Msaa, Visibility, VisibleEntities},
Extract, ExtractSchedule, RenderApp, RenderSet,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_transform::components::{GlobalTransform, Transform};
use bevy_utils::{FloatOrd, HashMap, HashSet};
@ -161,13 +161,16 @@ where
.init_resource::<ExtractedMaterials2d<M>>()
.init_resource::<RenderMaterials2d<M>>()
.init_resource::<SpecializedMeshPipelines<Material2dPipeline<M>>>()
.add_systems((
extract_materials_2d::<M>.in_schedule(ExtractSchedule),
prepare_materials_2d::<M>
.in_set(RenderSet::Prepare)
.after(PrepareAssetSet::PreAssetPrepare),
queue_material2d_meshes::<M>.in_set(RenderSet::Queue),
));
.add_systems(ExtractSchedule, extract_materials_2d::<M>)
.add_systems(
Render,
(
prepare_materials_2d::<M>
.in_set(RenderSet::Prepare)
.after(PrepareAssetSet::PreAssetPrepare),
queue_material2d_meshes::<M>.in_set(RenderSet::Queue),
),
);
}
}
}

View file

@ -1,4 +1,4 @@
use bevy_app::{IntoSystemAppConfig, Plugin};
use bevy_app::Plugin;
use bevy_asset::{load_internal_asset, Handle, HandleUntyped};
use bevy_ecs::{
@ -22,7 +22,7 @@ use bevy_render::{
view::{
ComputedVisibility, ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms,
},
Extract, ExtractSchedule, RenderApp, RenderSet,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_transform::components::GlobalTransform;
@ -103,11 +103,14 @@ impl Plugin for Mesh2dRenderPlugin {
render_app
.init_resource::<Mesh2dPipeline>()
.init_resource::<SpecializedMeshPipelines<Mesh2dPipeline>>()
.add_systems((
extract_mesh2d.in_schedule(ExtractSchedule),
queue_mesh2d_bind_group.in_set(RenderSet::Queue),
queue_mesh2d_view_bind_groups.in_set(RenderSet::Queue),
));
.add_systems(ExtractSchedule, extract_mesh2d)
.add_systems(
Render,
(
queue_mesh2d_bind_group.in_set(RenderSet::Queue),
queue_mesh2d_view_bind_groups.in_set(RenderSet::Queue),
),
);
}
}
}

View file

@ -79,9 +79,9 @@ impl Plugin for TextPlugin {
.init_resource::<TextSettings>()
.init_resource::<FontAtlasWarning>()
.insert_resource(TextPipeline::default())
.add_system(
.add_systems(
PostUpdate,
update_text2d_layout
.in_base_set(CoreSet::PostUpdate)
// Potential conflict: `Assets<Image>`
// In practice, they run independently since `bevy_render::camera_update_system`
// will only ever observe its own render target, and `update_text2d_layout`
@ -90,10 +90,9 @@ impl Plugin for TextPlugin {
);
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.add_system(
extract_text2d_sprite
.after(SpriteSystem::ExtractSprites)
.in_schedule(ExtractSchedule),
render_app.add_systems(
ExtractSchedule,
extract_text2d_sprite.after(SpriteSystem::ExtractSprites),
);
}
}

View file

@ -8,14 +8,14 @@ use bevy_utils::Duration;
/// If used for a fixed timestep system, use [`on_fixed_timer`] instead.
///
/// ```rust,no_run
/// # use bevy_app::{App, IntoSystemAppConfig, NoopPluginGroup as DefaultPlugins, PluginGroup};
/// # use bevy_ecs::schedule::IntoSystemConfig;
/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup, Update};
/// # use bevy_ecs::schedule::IntoSystemConfigs;
/// # use bevy_utils::Duration;
/// # use bevy_time::common_conditions::on_timer;
/// fn main() {
/// App::new()
/// .add_plugins(DefaultPlugins)
/// .add_system(tick.run_if(on_timer(Duration::from_secs(1))))
/// .add_systems(Update, tick.run_if(on_timer(Duration::from_secs(1))))
/// .run();
/// }
/// fn tick() {
@ -46,16 +46,15 @@ pub fn on_timer(duration: Duration) -> impl FnMut(Res<Time>) -> bool + Clone {
/// If used for a non-fixed timestep system, use [`on_timer`] instead.
///
/// ```rust,no_run
/// # use bevy_app::{App, CoreSchedule, IntoSystemAppConfig, NoopPluginGroup as DefaultPlugins, PluginGroup};
/// # use bevy_ecs::schedule::IntoSystemConfig;
/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup, FixedUpdate};
/// # use bevy_ecs::schedule::IntoSystemConfigs;
/// # use bevy_utils::Duration;
/// # use bevy_time::common_conditions::on_fixed_timer;
/// fn main() {
/// App::new()
/// .add_plugins(DefaultPlugins)
/// .add_system(
/// tick.in_schedule(CoreSchedule::FixedUpdate)
/// .run_if(on_fixed_timer(Duration::from_secs(1))),
/// .add_systems(FixedUpdate,
/// tick.run_if(on_fixed_timer(Duration::from_secs(1))),
/// )
/// .run();
/// }

View file

@ -1,8 +1,8 @@
//! Tools to run systems at a regular interval.
//! This can be extremely useful for steady, frame-rate independent gameplay logic and physics.
//!
//! To run a system on a fixed timestep, add it to the [`CoreSchedule::FixedUpdate`] [`Schedule`](bevy_ecs::schedule::Schedule).
//! This schedules is run in the [`CoreSet::FixedUpdate`](bevy_app::CoreSet::FixedUpdate) near the start of each frame,
//! To run a system on a fixed timestep, add it to the [`FixedUpdate`] [`Schedule`](bevy_ecs::schedule::Schedule).
//! This schedule is run in [`RunFixedUpdateLoop`](bevy_app::RunFixedUpdateLoop) near the start of each frame,
//! via the [`run_fixed_update_schedule`] exclusive system.
//!
//! This schedule will be run a number of times each frame,
@ -22,7 +22,7 @@
//! variants for game simulation, but rather use the value of [`FixedTime`] instead.
use crate::Time;
use bevy_app::CoreSchedule;
use bevy_app::FixedUpdate;
use bevy_ecs::{system::Resource, world::World};
use bevy_utils::Duration;
use thiserror::Error;
@ -98,7 +98,7 @@ pub enum FixedUpdateError {
},
}
/// Ticks the [`FixedTime`] resource then runs the [`CoreSchedule::FixedUpdate`].
/// Ticks the [`FixedTime`] resource then runs the [`FixedUpdate`].
pub fn run_fixed_update_schedule(world: &mut World) {
// Tick the time
let delta_time = world.resource::<Time>().delta();
@ -111,7 +111,7 @@ pub fn run_fixed_update_schedule(world: &mut World) {
let mut fixed_time = world.resource_mut::<FixedTime>();
let fixed_time_run = fixed_time.expend().is_ok();
if fixed_time_run {
world.run_schedule(CoreSchedule::FixedUpdate);
let _ = world.try_run_schedule(FixedUpdate);
} else {
check_again = false;
}

View file

@ -6,7 +6,7 @@ mod stopwatch;
mod time;
mod timer;
use fixed_timestep::{run_fixed_update_schedule, FixedTime};
use fixed_timestep::FixedTime;
pub use stopwatch::*;
pub use time::*;
pub use timer::*;
@ -21,9 +21,11 @@ pub mod prelude {
pub use crate::{fixed_timestep::FixedTime, Time, Timer, TimerMode};
}
use bevy_app::prelude::*;
use bevy_app::{prelude::*, RunFixedUpdateLoop};
use bevy_ecs::prelude::*;
use crate::fixed_timestep::run_fixed_update_schedule;
/// Adds time functionality to Apps.
#[derive(Default)]
pub struct TimePlugin;
@ -41,9 +43,8 @@ impl Plugin for TimePlugin {
.register_type::<Time>()
.register_type::<Stopwatch>()
.init_resource::<FixedTime>()
.configure_set(TimeSystem.in_base_set(CoreSet::First))
.add_system(time_system.in_set(TimeSystem))
.add_system(run_fixed_update_schedule.in_base_set(CoreSet::FixedUpdate));
.add_systems(First, time_system.in_set(TimeSystem))
.add_systems(RunFixedUpdateLoop, run_fixed_update_schedule);
}
}

View file

@ -121,7 +121,7 @@ impl Time {
/// world.insert_resource(Health { health_value: 0.2 });
///
/// let mut schedule = Schedule::new();
/// schedule.add_system(health_system);
/// schedule.add_systems(health_system);
///
/// // Simulate that 30 ms have passed
/// let mut time = world.resource_mut::<Time>();

View file

@ -24,7 +24,7 @@ use bevy_reflect::{std_traits::ReflectDefault, FromReflect, Reflect};
/// [`GlobalTransform`] is updated from [`Transform`] by systems in the system set
/// [`TransformPropagate`](crate::TransformSystem::TransformPropagate).
///
/// This system runs during [`CoreSet::PostUpdate`](crate::CoreSet::PostUpdate). If you
/// This system runs during [`PostUpdate`](bevy_app::PostUpdate). If you
/// update the [`Transform`] of an entity in this stage or after, you will notice a 1 frame lag
/// before the [`GlobalTransform`] is updated.
///

View file

@ -23,7 +23,7 @@ use std::ops::Mul;
/// [`GlobalTransform`] is updated from [`Transform`] by systems in the system set
/// [`TransformPropagate`](crate::TransformSystem::TransformPropagate).
///
/// This system runs during [`CoreSet::PostUpdate`](crate::CoreSet::PostUpdate). If you
/// This system runs during [`PostUpdate`](bevy_app::PostUpdate). If you
/// update the [`Transform`] of an entity during this set or after, you will notice a 1 frame lag
/// before the [`GlobalTransform`] is updated.
///

View file

@ -40,7 +40,7 @@ use systems::{propagate_transforms, sync_simple_transforms};
/// [`GlobalTransform`] is updated from [`Transform`] by systems in the system set
/// [`TransformPropagate`](crate::TransformSystem::TransformPropagate).
///
/// This system runs during [`CoreSet::PostUpdate`](crate::CoreSet::PostUpdate). If you
/// This system runs during [`PostUpdate`](bevy_app::PostUpdate). If you
/// update the [`Transform`] of an entity in this stage or after, you will notice a 1 frame lag
/// before the [`GlobalTransform`] is updated.
#[derive(Bundle, Clone, Copy, Debug, Default)]
@ -61,7 +61,7 @@ impl TransformBundle {
/// Creates a new [`TransformBundle`] from a [`Transform`].
///
/// This initializes [`GlobalTransform`] as identity, to be updated later by the
/// [`CoreSet::PostUpdate`](crate::CoreSet::PostUpdate) stage.
/// [`PostUpdate`](bevy_app::PostUpdate) stage.
#[inline]
pub const fn from_transform(transform: Transform) -> Self {
TransformBundle {
@ -98,28 +98,35 @@ impl Plugin for TransformPlugin {
app.register_type::<Transform>()
.register_type::<GlobalTransform>()
.add_plugin(ValidParentCheckPlugin::<GlobalTransform>::default())
.configure_set(
PostStartup,
PropagateTransformsSet.in_set(TransformSystem::TransformPropagate),
)
// add transform systems to startup so the first update is "correct"
.configure_set(TransformSystem::TransformPropagate.in_base_set(CoreSet::PostUpdate))
.configure_set(PropagateTransformsSet.in_set(TransformSystem::TransformPropagate))
.edit_schedule(CoreSchedule::Startup, |schedule| {
schedule.configure_set(
TransformSystem::TransformPropagate.in_base_set(StartupSet::PostStartup),
);
})
.add_startup_systems((
sync_simple_transforms
.in_set(TransformSystem::TransformPropagate)
// FIXME: https://github.com/bevyengine/bevy/issues/4381
// These systems cannot access the same entities,
// due to subtle query filtering that is not yet correctly computed in the ambiguity detector
.ambiguous_with(PropagateTransformsSet),
propagate_transforms.in_set(PropagateTransformsSet),
))
.add_systems((
sync_simple_transforms
.in_set(TransformSystem::TransformPropagate)
.ambiguous_with(PropagateTransformsSet),
propagate_transforms.in_set(PropagateTransformsSet),
));
.add_systems(
PostStartup,
(
sync_simple_transforms
.in_set(TransformSystem::TransformPropagate)
// FIXME: https://github.com/bevyengine/bevy/issues/4381
// These systems cannot access the same entities,
// due to subtle query filtering that is not yet correctly computed in the ambiguity detector
.ambiguous_with(PropagateTransformsSet),
propagate_transforms.in_set(PropagateTransformsSet),
),
)
.configure_set(
PostUpdate,
PropagateTransformsSet.in_set(TransformSystem::TransformPropagate),
)
.add_systems(
PostUpdate,
(
sync_simple_transforms
.in_set(TransformSystem::TransformPropagate)
.ambiguous_with(PropagateTransformsSet),
propagate_transforms.in_set(PropagateTransformsSet),
),
);
}
}

View file

@ -325,7 +325,7 @@ mod test {
let mut app = App::new();
ComputeTaskPool::init(TaskPool::default);
app.add_systems((sync_simple_transforms, propagate_transforms));
app.add_systems(Update, (sync_simple_transforms, propagate_transforms));
let translation = vec3(1.0, 0.0, 0.0);
@ -371,7 +371,7 @@ mod test {
let mut temp = World::new();
let mut app = App::new();
app.add_systems((propagate_transforms, sync_simple_transforms));
app.add_systems(Update, (propagate_transforms, sync_simple_transforms));
fn setup_world(world: &mut World) -> (Entity, Entity) {
let mut grandchild = Entity::from_raw(0);

View file

@ -1,25 +1,22 @@
use crate::{
prelude::{Button, Label},
Node, UiImage,
};
use bevy_a11y::{
accesskit::{NodeBuilder, Rect, Role},
AccessibilityNode,
};
use bevy_app::{App, Plugin};
use bevy_app::{App, Plugin, Update};
use bevy_ecs::{
prelude::Entity,
query::{Changed, Or, Without},
system::{Commands, Query},
};
use bevy_hierarchy::Children;
use bevy_render::prelude::Camera;
use bevy_text::Text;
use bevy_transform::prelude::GlobalTransform;
use crate::{
prelude::{Button, Label},
Node, UiImage,
};
fn calc_name(texts: &Query<&Text>, children: &Children) -> Option<Box<str>> {
let mut name = None;
for child in children.iter() {
@ -149,6 +146,9 @@ pub(crate) struct AccessibilityPlugin;
impl Plugin for AccessibilityPlugin {
fn build(&self, app: &mut App) {
app.add_systems((calc_bounds, button_changed, image_changed, label_changed));
app.add_systems(
Update,
(calc_bounds, button_changed, image_changed, label_changed),
);
}
}

View file

@ -104,15 +104,15 @@ impl Plugin for UiPlugin {
.register_type::<Val>()
.register_type::<widget::Button>()
.register_type::<widget::Label>()
.configure_set(UiSystem::Focus.in_base_set(CoreSet::PreUpdate))
.configure_set(UiSystem::Flex.in_base_set(CoreSet::PostUpdate))
.configure_set(UiSystem::Stack.in_base_set(CoreSet::PostUpdate))
.add_system(ui_focus_system.in_set(UiSystem::Focus).after(InputSystem));
.add_systems(
PreUpdate,
ui_focus_system.in_set(UiSystem::Focus).after(InputSystem),
);
// add these systems to front because these must run before transform update systems
#[cfg(feature = "bevy_text")]
app.add_system(
app.add_systems(
PostUpdate,
widget::text_system
.in_base_set(CoreSet::PostUpdate)
.before(UiSystem::Flex)
// Potential conflict: `Assets<Image>`
// In practice, they run independently since `bevy_render::camera_update_system`
@ -126,10 +126,8 @@ impl Plugin for UiPlugin {
);
#[cfg(feature = "bevy_text")]
app.add_plugin(accessibility::AccessibilityPlugin);
app.add_system({
let system = widget::update_image_calculated_size_system
.in_base_set(CoreSet::PostUpdate)
.before(UiSystem::Flex);
app.add_systems(PostUpdate, {
let system = widget::update_image_calculated_size_system.before(UiSystem::Flex);
// Potential conflicts: `Assets<Image>`
// They run independently since `widget::image_node_system` will only ever observe
// its own UiImage, and `widget::text_system` & `bevy_text::update_text2d_layout`
@ -141,15 +139,16 @@ impl Plugin for UiPlugin {
system
})
.add_systems((
flex_node_system
.in_set(UiSystem::Flex)
.before(TransformSystem::TransformPropagate),
ui_stack_system.in_set(UiSystem::Stack),
update_clipping_system
.after(TransformSystem::TransformPropagate)
.in_base_set(CoreSet::PostUpdate),
));
.add_systems(
PostUpdate,
(
flex_node_system
.in_set(UiSystem::Flex)
.before(TransformSystem::TransformPropagate),
ui_stack_system.in_set(UiSystem::Stack),
update_clipping_system.after(TransformSystem::TransformPropagate),
),
);
crate::render::build_ui_render(app);
}

View file

@ -2,7 +2,7 @@ mod pipeline;
mod render_pass;
use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};
use bevy_render::ExtractSchedule;
use bevy_render::{ExtractSchedule, Render};
#[cfg(feature = "bevy_text")]
use bevy_window::{PrimaryWindow, Window};
pub use pipeline::*;
@ -77,20 +77,23 @@ pub fn build_ui_render(app: &mut App) {
.init_resource::<DrawFunctions<TransparentUi>>()
.add_render_command::<TransparentUi, DrawUi>()
.add_systems(
ExtractSchedule,
(
extract_default_ui_camera_view::<Camera2d>,
extract_default_ui_camera_view::<Camera3d>,
extract_uinodes.in_set(RenderUiSystem::ExtractNode),
#[cfg(feature = "bevy_text")]
extract_text_uinodes.after(RenderUiSystem::ExtractNode),
)
.in_schedule(ExtractSchedule),
),
)
.add_systems((
prepare_uinodes.in_set(RenderSet::Prepare),
queue_uinodes.in_set(RenderSet::Queue),
sort_phase_system::<TransparentUi>.in_set(RenderSet::PhaseSort),
));
.add_systems(
Render,
(
prepare_uinodes.in_set(RenderSet::Prepare),
queue_uinodes.in_set(RenderSet::Queue),
sort_phase_system::<TransparentUi>.in_set(RenderSet::PhaseSort),
),
);
// Render graph
let ui_graph_2d = get_ui_graph(render_app);

View file

@ -196,7 +196,7 @@ mod tests {
queue.apply(&mut world);
let mut schedule = Schedule::default();
schedule.add_system(ui_stack_system);
schedule.add_systems(ui_stack_system);
schedule.run(&mut world);
let mut query = world.query::<&Label>();

View file

@ -22,7 +22,6 @@ pub mod prelude {
}
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use std::path::PathBuf;
impl Default for WindowPlugin {
@ -53,14 +52,14 @@ pub struct WindowPlugin {
/// surprise your users. It is recommended to leave this setting to
/// either [`ExitCondition::OnAllClosed`] or [`ExitCondition::OnPrimaryClosed`].
///
/// [`ExitCondition::OnAllClosed`] will add [`exit_on_all_closed`] to [`CoreSet::Update`].
/// [`ExitCondition::OnPrimaryClosed`] will add [`exit_on_primary_closed`] to [`CoreSet::Update`].
/// [`ExitCondition::OnAllClosed`] will add [`exit_on_all_closed`] to [`Update`].
/// [`ExitCondition::OnPrimaryClosed`] will add [`exit_on_primary_closed`] to [`Update`].
pub exit_condition: ExitCondition,
/// Whether to close windows when they are requested to be closed (i.e.
/// when the close button is pressed).
///
/// If true, this plugin will add [`close_when_requested`] to [`CoreSet::Update`].
/// If true, this plugin will add [`close_when_requested`] to [`Update`].
/// If this system (or a replacement) is not running, the close button will have no effect.
/// This may surprise your users. It is recommended to leave this setting as `true`.
pub close_when_requested: bool,
@ -93,17 +92,17 @@ impl Plugin for WindowPlugin {
match self.exit_condition {
ExitCondition::OnPrimaryClosed => {
app.add_system(exit_on_primary_closed.in_base_set(CoreSet::PostUpdate));
app.add_systems(PostUpdate, exit_on_primary_closed);
}
ExitCondition::OnAllClosed => {
app.add_system(exit_on_all_closed.in_base_set(CoreSet::PostUpdate));
app.add_systems(PostUpdate, exit_on_all_closed);
}
ExitCondition::DontExit => {}
}
if self.close_when_requested {
// Need to run before `exit_on_*` systems
app.add_system(close_when_requested);
app.add_systems(Update, close_when_requested);
}
// Register event types
@ -143,11 +142,11 @@ impl Plugin for WindowPlugin {
pub enum ExitCondition {
/// Close application when the primary window is closed
///
/// The plugin will add [`exit_on_primary_closed`] to [`CoreSet::Update`].
/// The plugin will add [`exit_on_primary_closed`] to [`Update`].
OnPrimaryClosed,
/// Close application when all windows are closed
///
/// The plugin will add [`exit_on_all_closed`] to [`CoreSet::Update`].
/// The plugin will add [`exit_on_all_closed`] to [`Update`].
OnAllClosed,
/// Keep application running headless even after closing all windows
///

View file

@ -8,7 +8,7 @@ use bevy_a11y::{
accesskit::{ActionHandler, ActionRequest, NodeBuilder, NodeClassSet, Role, TreeUpdate},
AccessKitEntityExt, AccessibilityNode, AccessibilityRequested, Focus,
};
use bevy_app::{App, Plugin};
use bevy_app::{App, Plugin, Update};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
prelude::{DetectChanges, Entity, EventReader, EventWriter},
@ -163,11 +163,14 @@ impl Plugin for AccessibilityPlugin {
app.init_non_send_resource::<AccessKitAdapters>()
.init_resource::<WinitActionHandlers>()
.add_event::<ActionRequest>()
.add_systems((
handle_window_focus,
window_closed,
poll_receivers,
update_accessibility_nodes,
));
.add_systems(
Update,
(
handle_window_focus,
window_closed,
poll_receivers,
update_accessibility_nodes,
),
);
}
}

View file

@ -13,7 +13,7 @@ use system::{changed_window, create_window, despawn_window, CachedWindow};
pub use winit_config::*;
pub use winit_windows::*;
use bevy_app::{App, AppExit, CoreSet, Plugin};
use bevy_app::{App, AppExit, Last, Plugin};
use bevy_ecs::event::{Events, ManualEventReader};
use bevy_ecs::prelude::*;
use bevy_input::{
@ -76,12 +76,12 @@ impl Plugin for WinitPlugin {
// exit_on_all_closed only uses the query to determine if the query is empty,
// and so doesn't care about ordering relative to changed_window
.add_systems(
Last,
(
changed_window.ambiguous_with(exit_on_all_closed),
// Update the state of the window before attempting to despawn to ensure consistent event ordering
despawn_window.after(changed_window),
)
.in_base_set(CoreSet::Last),
),
);
app.add_plugin(AccessibilityPlugin);

View file

@ -1,5 +1,5 @@
use crate::WinitWindows;
use bevy_app::{App, Plugin};
use bevy_app::{App, Plugin, Update};
use bevy_ecs::prelude::*;
use crossbeam_channel::{Receiver, Sender};
use wasm_bindgen::JsCast;
@ -10,7 +10,7 @@ pub(crate) struct CanvasParentResizePlugin;
impl Plugin for CanvasParentResizePlugin {
fn build(&self, app: &mut App) {
app.init_resource::<CanvasParentResizeEventChannel>()
.add_system(canvas_parent_resize_event_handler);
.add_systems(Update, canvas_parent_resize_event_handler);
}
}

View file

@ -23,7 +23,7 @@ fn move_enemies_to_player(
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_system(move_enemies_to_player)
.add_systems(Update, move_enemies_to_player)
.run();
}
```
@ -55,7 +55,7 @@ fn move_enemies_to_player(
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_system(move_enemies_to_player)
.add_systems(Update, move_enemies_to_player)
.run();
}
```
@ -85,7 +85,7 @@ fn move_enemies_to_player(
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_system(move_enemies_to_player)
.add_systems(Update, move_enemies_to_player)
.run();
}
```

View file

@ -17,7 +17,7 @@ fn update_materials(
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_system(update_materials)
.add_systems(Update, update_materials)
.run();
}
```
@ -38,7 +38,7 @@ fn update_materials(
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_system(update_materials)
.add_systems(Update, update_materials)
.run();
}
```

View file

@ -10,9 +10,9 @@ use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.add_system(despawning)
.add_system(use_entity.after(despawning))
.add_systems(Startup, setup)
.add_systems(Update, despawning)
.add_systems(Update, use_entity.after(despawning))
.run();
}

View file

@ -50,7 +50,7 @@ fn setup_cube(
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(setup_cube)
.add_systems(Startup, setup_cube)
.run();
}
```
@ -97,7 +97,7 @@ fn setup_cube(
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(setup_cube)
.add_systems(Startup, setup_cube)
.run();
}
```

View file

@ -5,7 +5,7 @@ use bevy::{prelude::*, sprite::MaterialMesh2dBundle};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.add_systems(Startup, setup)
.run();
}

View file

@ -13,7 +13,8 @@ fn main() {
App::new()
.insert_resource(ClearColor(Color::DARK_GRAY))
.add_plugins(DefaultPlugins)
.add_systems((setup.on_startup(), update_bloom_settings))
.add_systems(Startup, setup)
.add_systems(Update, update_bloom_settings)
.run();
}

View file

@ -5,7 +5,7 @@ use bevy::{prelude::*, sprite::MaterialMesh2dBundle};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.add_systems(Startup, setup)
.run();
}

Some files were not shown because too many files have changed in this diff Show more