diff --git a/Cargo.toml b/Cargo.toml index c313984691f8b..7419f45fe7d3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ default = [ "vorbis", "x11", "filesystem_watcher", + "bevy_gizmos", "android_shared_stdcxx", "tonemapping_luts", ] @@ -66,7 +67,7 @@ bevy_asset = ["bevy_internal/bevy_asset"] bevy_audio = ["bevy_internal/bevy_audio"] # Provides cameras and other basic render pipeline features -bevy_core_pipeline = ["bevy_internal/bevy_core_pipeline"] +bevy_core_pipeline = ["bevy_internal/bevy_core_pipeline", "bevy_asset", "bevy_render"] # Plugin for dynamic loading (using [libloading](https://crates.io/crates/libloading)) bevy_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"] @@ -75,29 +76,32 @@ bevy_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"] bevy_gilrs = ["bevy_internal/bevy_gilrs"] # [glTF](https://www.khronos.org/gltf/) support -bevy_gltf = ["bevy_internal/bevy_gltf"] +bevy_gltf = ["bevy_internal/bevy_gltf", "bevy_asset", "bevy_scene", "bevy_pbr"] # Adds PBR rendering -bevy_pbr = ["bevy_internal/bevy_pbr"] +bevy_pbr = ["bevy_internal/bevy_pbr", "bevy_asset", "bevy_render", "bevy_core_pipeline"] # Provides rendering functionality bevy_render = ["bevy_internal/bevy_render"] # Provides scene functionality -bevy_scene = ["bevy_internal/bevy_scene"] +bevy_scene = ["bevy_internal/bevy_scene", "bevy_asset"] # Provides sprite functionality -bevy_sprite = ["bevy_internal/bevy_sprite"] +bevy_sprite = ["bevy_internal/bevy_sprite", "bevy_render", "bevy_core_pipeline"] # Provides text functionality bevy_text = ["bevy_internal/bevy_text"] # A custom ECS-driven UI framework -bevy_ui = ["bevy_internal/bevy_ui"] +bevy_ui = ["bevy_internal/bevy_ui", "bevy_core_pipeline", "bevy_text", "bevy_sprite"] # winit window and input backend bevy_winit = ["bevy_internal/bevy_winit"] +# Adds support for rendering gizmos +bevy_gizmos = ["bevy_internal/bevy_gizmos"] + # Tracing support, saving a file in Chrome Tracing format trace_chrome = ["trace", "bevy_internal/trace_chrome"] @@ -309,6 +313,16 @@ description = "Renders a rectangle, circle, and hexagon" category = "2D Rendering" wasm = true +[[example]] +name = "2d_gizmos" +path = "examples/2d/2d_gizmos.rs" + +[package.metadata.example.2d_gizmos] +name = "2D Gizmos" +description = "A scene showcasing 2D gizmos" +category = "2D Rendering" +wasm = true + [[example]] name = "sprite" path = "examples/2d/sprite.rs" @@ -410,6 +424,16 @@ description = "A scene showcasing the built-in 3D shapes" category = "3D Rendering" wasm = true +[[example]] +name = "3d_gizmos" +path = "examples/3d/3d_gizmos.rs" + +[package.metadata.example.3d_gizmos] +name = "3D Gizmos" +description = "A scene showcasing 3D gizmos" +category = "3D Rendering" +wasm = true + [[example]] name = "atmospheric_fog" path = "examples/3d/atmospheric_fog.rs" @@ -1395,11 +1419,21 @@ name = "post_processing" path = "examples/shader/post_processing.rs" [package.metadata.example.post_processing] -name = "Post Processing" +name = "Post Processing - Render To Texture" description = "A custom post processing effect, using two cameras, with one reusing the render texture of the first one" category = "Shaders" wasm = true +[[example]] +name = "post_process_pass" +path = "examples/shader/post_process_pass.rs" + +[package.metadata.example.post_process_pass] +name = "Post Processing - Custom Render Pass" +description = "A custom post processing effect, using a custom render pass that runs after the main pass" +category = "Shaders" +wasm = true + [[example]] name = "shader_defs" path = "examples/shader/shader_defs.rs" @@ -1553,6 +1587,16 @@ description = "Simple benchmark to test per-entity draw overhead. Run with the ` category = "Stress Tests" wasm = true +[[example]] +name = "many_gizmos" +path = "examples/stress_tests/many_gizmos.rs" + +[package.metadata.example.many_gizmos] +name = "Many Gizmos" +description = "Test rendering of many gizmos" +category = "Stress Tests" +wasm = true + [[example]] name = "many_foxes" path = "examples/stress_tests/many_foxes.rs" @@ -1563,6 +1607,16 @@ description = "Loads an animated fox model and spawns lots of them. Good for tes category = "Stress Tests" wasm = true +[[example]] +name = "many_glyphs" +path = "examples/stress_tests/many_glyphs.rs" + +[package.metadata.example.many_glyphs] +name = "Many Glyphs" +description = "Simple benchmark to test text rendering." +category = "Stress Tests" +wasm = true + [[example]] name = "many_lights" path = "examples/stress_tests/many_lights.rs" @@ -1736,12 +1790,12 @@ category = "UI (User Interface)" wasm = true [[example]] -name = "text_layout" -path = "examples/ui/text_layout.rs" +name = "flex_layout" +path = "examples/ui/flex_layout.rs" -[package.metadata.example.text_layout] -name = "Text Layout" -description = "Demonstrates how the AlignItems and JustifyContent properties can be composed to layout text" +[package.metadata.example.flex_layout] +name = "Flex Layout" +description = "Demonstrates how the AlignItems and JustifyContent properties can be composed to layout nodes and position text" category = "UI (User Interface)" wasm = false diff --git a/assets/scenes/load_scene_example.scn.ron b/assets/scenes/load_scene_example.scn.ron index de4d3ce9280a0..4c03d1c53cc1a 100644 --- a/assets/scenes/load_scene_example.scn.ron +++ b/assets/scenes/load_scene_example.scn.ron @@ -1,4 +1,9 @@ ( + resources: { + "scene::ResourceA": ( + score: 2, + ), + }, entities: { 0: ( components: { diff --git a/assets/shaders/post_process_pass.wgsl b/assets/shaders/post_process_pass.wgsl new file mode 100644 index 0000000000000..b25b5788cc8a6 --- /dev/null +++ b/assets/shaders/post_process_pass.wgsl @@ -0,0 +1,48 @@ +// This shader computes the chromatic aberration effect + +#import bevy_pbr::utils + +// Since post processing is a fullscreen effect, we use the fullscreen vertex shader provided by bevy. +// This will import a vertex shader that renders a single fullscreen triangle. +// +// A fullscreen triangle is a single triangle that covers the entire screen. +// The box in the top left in that diagram is the screen. The 4 x are the corner of the screen +// +// Y axis +// 1 | x-----x...... +// 0 | | s | . ´ +// -1 | x_____x´ +// -2 | : .´ +// -3 | :´ +// +--------------- X axis +// -1 0 1 2 3 +// +// As you can see, the triangle ends up bigger than the screen. +// +// You don't need to worry about this too much since bevy will compute the correct UVs for you. +#import bevy_core_pipeline::fullscreen_vertex_shader + +@group(0) @binding(0) +var screen_texture: texture_2d; +@group(0) @binding(1) +var texture_sampler: sampler; +struct PostProcessSettings { + intensity: f32, +} +@group(0) @binding(2) +var settings: PostProcessSettings; + +@fragment +fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4 { + // Chromatic aberration strength + let offset_strength = settings.intensity; + + // Sample each color channel with an arbitrary shift + return vec4( + textureSample(screen_texture, texture_sampler, in.uv + vec2(offset_strength, -offset_strength)).r, + textureSample(screen_texture, texture_sampler, in.uv + vec2(-offset_strength, 0.0)).g, + textureSample(screen_texture, texture_sampler, in.uv + vec2(0.0, offset_strength)).b, + 1.0 + ); +} + diff --git a/benches/benches/bevy_ecs/components/archetype_updates.rs b/benches/benches/bevy_ecs/components/archetype_updates.rs index 53cc22f6a1adb..30f8ca6fccb97 100644 --- a/benches/benches/bevy_ecs/components/archetype_updates.rs +++ b/benches/benches/bevy_ecs/components/archetype_updates.rs @@ -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) diff --git a/benches/benches/bevy_ecs/empty_archetypes.rs b/benches/benches/bevy_ecs/empty_archetypes.rs index 0d01bdcc32830..315a2d2ce2b68 100644 --- a/benches/benches/bevy_ecs/empty_archetypes.rs +++ b/benches/benches/bevy_ecs/empty_archetypes.rs @@ -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(); diff --git a/benches/benches/bevy_ecs/scheduling/run_condition.rs b/benches/benches/bevy_ecs/scheduling/run_condition.rs index 367cd7839e394..a18bdd8c97f40 100644 --- a/benches/benches/bevy_ecs/scheduling/run_condition.rs +++ b/benches/benches/bevy_ecs/scheduling/run_condition.rs @@ -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), diff --git a/benches/benches/bevy_ecs/scheduling/running_systems.rs b/benches/benches/bevy_ecs/scheduling/running_systems.rs index 99a0827c2a6c5..151b96d4a6af4 100644 --- a/benches/benches/bevy_ecs/scheduling/running_systems.rs +++ b/benches/benches/bevy_ecs/scheduling/running_systems.rs @@ -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| { diff --git a/benches/benches/bevy_ecs/scheduling/schedule.rs b/benches/benches/bevy_ecs/scheduling/schedule.rs index 5d2ab876775a2..e670c23d74b37 100644 --- a/benches/benches/bevy_ecs/scheduling/schedule.rs +++ b/benches/benches/bevy_ecs/scheduling/schedule.rs @@ -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. diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 7312a5fcf659f..0e9534d63b237 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -5,7 +5,7 @@ use std::ops::Deref; use std::time::Duration; -use bevy_app::{App, 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::() .register_asset_reflect::() .register_type::() - .add_system( - animation_player - .in_base_set(CoreSet::PostUpdate) - .before(TransformSystem::TransformPropagate), + .add_systems( + PostUpdate, + animation_player.before(TransformSystem::TransformPropagate), ); } } diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 9ffc7a7c2305e..d7a4da63cd1b0 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1,18 +1,20 @@ 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, }, }; use bevy_utils::{tracing::debug, HashMap, HashSet}; -use std::fmt::Debug; +use std::{ + fmt::Debug, + panic::{catch_unwind, resume_unwind, AssertUnwindSafe}, +}; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; @@ -50,7 +52,7 @@ pub(crate) enum AppError { /// # /// fn main() { /// App::new() -/// .add_system(hello_world_system) +/// .add_systems(Update, hello_world_system) /// .run(); /// } /// @@ -71,17 +73,15 @@ pub struct App { pub runner: Box, // 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, plugin_registry: Vec>, plugin_name_added: HashSet, - /// A private marker to prevent incorrect calls to `App::run()` from `Plugin::build()` - is_building_plugin: bool, + /// A private counter to prevent incorrect calls to `App::run()` from `Plugin::build()` + building_plugin_depth: usize, } impl Debug for App { @@ -101,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; /// @@ -119,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| { +/// sub_app.add_systems(Main, |counter: Res| { /// // since we assigned the value from the main world in extract /// // we see that value instead of 100 /// assert_eq!(counter.0, 10); @@ -166,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(); } @@ -192,8 +190,7 @@ impl Default for App { #[cfg(feature = "bevy_reflect")] app.init_resource::(); - app.add_default_schedules(); - + app.add_plugin(MainSchedulePlugin); app.add_event::(); #[cfg(feature = "bevy_ci_testing")] @@ -208,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() } @@ -226,9 +221,8 @@ impl App { sub_apps: HashMap::default(), plugin_registry: Vec::default(), plugin_name_added: Default::default(), - default_schedule_label: Box::new(CoreSchedule::Main), - outer_schedule_label: Box::new(CoreSchedule::Outer), - is_building_plugin: false, + main_schedule_label: Box::new(Main), + building_plugin_depth: 0, } } @@ -237,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 /// @@ -248,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")] @@ -291,8 +284,8 @@ impl App { let _bevy_app_run_span = info_span!("bevy_app").entered(); let mut app = std::mem::replace(self, App::empty()); - if app.is_building_plugin { - panic!("App::run() was called from within Plugin::Build(), which is not allowed."); + if app.building_plugin_depth > 0 { + panic!("App::run() was called from within Plugin::build(), which is not allowed."); } Self::setup(&mut app); @@ -314,13 +307,13 @@ impl App { /// Adds [`State`] and [`NextState`] resources, [`OnEnter`] and [`OnExit`] schedules /// for each state variant (if they don't already exist), an instance of [`apply_state_transition::`] in - /// [`CoreSet::StateTransitions`] so that transitions happen before [`CoreSet::Update`] and - /// a instance of [`run_enter_schedule::`] in [`CoreSet::StateTransitions`] with a + /// [`StateTransition`] so that transitions happen before [`Update`] and + /// a instance of [`run_enter_schedule::`] 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`] resource matches the respective state variant. /// /// If you would like to control how other systems run based on the current state, @@ -329,42 +322,25 @@ 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(&mut self) -> &mut Self { - self.init_resource::>(); - self.init_resource::>(); - - let mut schedules = self.world.resource_mut::(); - - 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::.run_if(run_once_condition()), - apply_state_transition::, - ) - .chain() - .in_base_set(CoreSet::StateTransitions), - ); - - for variant in S::variants() { - default_schedule.configure_set( - OnUpdate(variant.clone()) - .in_base_set(CoreSet::Update) - .run_if(in_state(variant)), + self.init_resource::>() + .init_resource::>() + .add_systems( + StateTransition, + ( + run_enter_schedule::.run_if(run_once_condition()), + apply_state_transition::, + ) + .chain(), ); - } - // These are different for loops to avoid conflicting access to self + for variant in S::variants() { - if self.get_schedule(OnEnter(variant.clone())).is_none() { - self.add_schedule(OnEnter(variant.clone()), Schedule::new()); - } - if self.get_schedule(OnExit(variant.clone())).is_none() { - self.add_schedule(OnExit(variant), Schedule::new()); - } + self.configure_set(Update, OnUpdate(variant.clone()).run_if(in_state(variant))); } + // The OnEnter, OnExit, and OnTransition schedules are lazily initialized + // (i.e. when the first system is added to them), and World::try_run_schedule is used to fail + // gracefully if they aren't present. + self } @@ -384,30 +360,15 @@ impl App { /// # /// app.add_system(my_system); /// ``` - pub fn add_system(&mut self, system: impl IntoSystemAppConfig) -> &mut Self { - let mut schedules = self.world.resource_mut::(); - - 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(&mut self, system: impl IntoSystemConfigs) -> &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 /// @@ -420,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(&mut self, systems: impl IntoSystemAppConfigs) -> &mut Self { + pub fn add_systems( + &mut self, + schedule: impl ScheduleLabel, + systems: impl IntoSystemConfigs, + ) -> &mut Self { let mut schedules = self.world.resource_mut::(); - 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). @@ -465,13 +417,17 @@ impl App { /// } /// /// App::new() - /// .add_startup_system(my_startup_system); + /// .add_systems(Startup, my_startup_system); /// ``` - pub fn add_startup_system(&mut self, system: impl IntoSystemConfig) -> &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(&mut self, system: impl IntoSystemConfigs) -> &mut Self { + self.add_systems(Startup, system) } - /// Adds a collection of systems to [`CoreSchedule::Startup`]. + /// Adds a collection of systems to [`Startup`]. /// /// # Examples /// @@ -484,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(&mut self, systems: impl IntoSystemConfigs) -> &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::() - .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::(); + 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::() - .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::() - /// .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::(); + 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::`], - /// 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. /// @@ -586,7 +512,7 @@ impl App { { if !self.world.contains_resource::>() { self.init_resource::>() - .add_system(Events::::update_system.in_base_set(CoreSet::First)); + .add_systems(First, Events::::update_system); } self } @@ -765,9 +691,12 @@ impl App { plugin_name: plugin.name().to_string(), })?; } - self.is_building_plugin = true; - plugin.build(self); - self.is_building_plugin = false; + self.building_plugin_depth += 1; + let result = catch_unwind(AssertUnwindSafe(|| plugin.build(self))); + self.building_plugin_depth -= 1; + if let Err(payload) = result { + resume_unwind(payload); + } self.plugin_registry.push(plugin); Ok(self) } @@ -1024,7 +953,7 @@ mod tests { system::Commands, }; - use crate::{App, IntoSystemAppConfig, IntoSystemAppConfigs, Plugin}; + use crate::{App, Plugin}; struct PluginA; impl Plugin for PluginA { @@ -1071,9 +1000,13 @@ mod tests { #[should_panic] fn cant_call_app_run_from_plugin_build() { struct PluginRun; + struct InnerPlugin; + impl Plugin for InnerPlugin { + fn build(&self, _: &mut crate::App) {} + } impl Plugin for PluginRun { fn build(&self, app: &mut crate::App) { - app.run(); + app.add_plugin(InnerPlugin).run(); } } App::new().add_plugin(PluginRun); @@ -1092,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::(); - - 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::() - .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::() - .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); @@ -1125,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::(); app.world.run_schedule(OnEnter(AppState::MainMenu)); diff --git a/crates/bevy_app/src/ci_testing.rs b/crates/bevy_app/src/ci_testing.rs index 17d8d929d6a4c..f662a350a4177 100644 --- a/crates/bevy_app/src/ci_testing.rs +++ b/crates/bevy_app/src/ci_testing.rs @@ -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 } diff --git a/crates/bevy_app/src/config.rs b/crates/bevy_app/src/config.rs deleted file mode 100644 index fb3a3fde1cd92..0000000000000 --- a/crates/bevy_app/src/config.rs +++ /dev/null @@ -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, -} - -/// Types that can be converted into a [`SystemAppConfig`]. -/// -/// This has been implemented for all `System` trait objects -/// and all functions that convert into such. -pub trait IntoSystemAppConfig: 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(self, set: impl IntoSystemSet) -> Self { - let Self { system, schedule } = self; - Self { - system: system.before(set), - schedule, - } - } - - fn after(self, set: impl IntoSystemSet) -> Self { - let Self { system, schedule } = self; - Self { - system: system.after(set), - schedule, - } - } - - fn run_if

(self, condition: impl Condition

) -> Self { - let Self { system, schedule } = self; - Self { - system: system.run_if(condition), - schedule, - } - } - - fn ambiguous_with(self, set: impl IntoSystemSet) -> 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 IntoSystemAppConfig for T -where - T: IntoSystemConfig, -{ - 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, - }, - /// This came from several separate instances of `SystemAppConfig`. - /// Each system gets its own schedule. - Granular(Vec), -} - -/// Types that can convert into [`SystemAppConfigs`]. -pub trait IntoSystemAppConfigs: 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); diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index 165e9ebd70e07..8094d0458eeb0 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -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) { - 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 - } -} diff --git a/crates/bevy_app/src/main_schedule.rs b/crates/bevy_app/src/main_schedule.rs new file mode 100644 index 0000000000000..2042b9e01f4e8 --- /dev/null +++ b/crates/bevy_app/src/main_schedule.rs @@ -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>, +} + +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) { + 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| { + 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::() + .add_systems(Main, Main::run_main); + } +} diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index dd6a8ae307b7b..e456c7eb4435a 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -644,7 +644,7 @@ pub fn free_unused_assets_system(asset_server: Res) { 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::.after(FreeUnusedAssets), - )); + app.add_systems( + Update, + ( + free_unused_assets_system.in_set(FreeUnusedAssets), + update_asset_storage_system::.after(FreeUnusedAssets), + ), + ); fn load_asset(path: AssetPath, world: &World) -> HandleUntyped { let asset_server = world.resource::(); diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 40d390187f80d..3fffa1d413747 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -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::::asset_event_system.in_base_set(AssetSet::AssetEvents), - update_asset_storage_system::.in_base_set(AssetSet::LoadAssets), - )) + .add_systems(LoadAssets, update_asset_storage_system::) + .add_systems(AssetEvents, Assets::::asset_event_system) .register_type::>() .add_event::>() } @@ -362,9 +360,9 @@ impl AddAsset for App { { #[cfg(feature = "debug_asset_server")] { - self.add_system( - crate::debug_asset_server::sync_debug_assets:: - .in_base_set(bevy_app::CoreSet::Update), + self.add_systems( + bevy_app::Update, + crate::debug_asset_server::sync_debug_assets::, ); let mut app = self .world diff --git a/crates/bevy_asset/src/debug_asset_server.rs b/crates/bevy_asset/src/debug_asset_server.rs index 7b506faeed37e..955fadb80f0ac 100644 --- a/crates/bevy_asset/src/debug_asset_server.rs +++ b/crates/bevy_asset/src/debug_asset_server.rs @@ -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); } } diff --git a/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs b/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs index d36cd2969d2d7..a5ce2546db60c 100644 --- a/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs +++ b/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs @@ -18,8 +18,8 @@ impl Default for AssetCountDiagnosticsPlugin { impl Plugin for AssetCountDiagnosticsPlugin { 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); } } diff --git a/crates/bevy_asset/src/io/file_asset_io.rs b/crates/bevy_asset/src/io/file_asset_io.rs index 93c6878c3147d..61003e2249fd4 100644 --- a/crates/bevy_asset/src/io/file_asset_io.rs +++ b/crates/bevy_asset/src/io/file_asset_io.rs @@ -127,6 +127,7 @@ impl AssetIo for FileAssetIo { to_watch: &Path, to_reload: Option, ) -> Result<(), AssetIoError> { + #![allow(unused_variables)] #[cfg(feature = "filesystem_watcher")] { let to_reload = to_reload.unwrap_or_else(|| to_watch.to_owned()); diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 59b0b2cb327f3..3265690165738 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -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::(); - - 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::() + .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::(); + order.insert_after(First, LoadAssets); + order.insert_after(PostUpdate, AssetEvents); } } diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index af751a8364c61..468ff3d622c24 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -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::() .add_asset::() .init_resource::>() - .add_system(play_queued_audio_system::.in_base_set(CoreSet::PostUpdate)); + .add_systems(PostUpdate, play_queued_audio_system::); #[cfg(any(feature = "mp3", feature = "flac", feature = "wav", feature = "vorbis"))] app.init_asset_loader::(); @@ -78,6 +77,6 @@ impl AddAudioSource for App { self.add_asset::() .init_resource::>() .init_resource::>() - .add_system(play_queued_audio_system::.in_base_set(CoreSet::PostUpdate)) + .add_systems(PostUpdate, play_queued_audio_system::) } } diff --git a/crates/bevy_core/src/lib.rs b/crates/bevy_core/src/lib.rs index 7e58685c9fe37..daf43cf86e63b 100644 --- a/crates/bevy_core/src/lib.rs +++ b/crates/bevy_core/src/lib.rs @@ -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>) { /// 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::(); - app.add_system(update_frame_count.in_base_set(CoreSet::Last)); + app.add_systems(Last, update_frame_count); } } diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index 5ff8cfba4e376..cc889d00f2e76 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -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::{ @@ -22,12 +16,12 @@ use bevy_render::{ ComponentUniforms, DynamicUniformIndex, ExtractComponentPlugin, UniformComponentPlugin, }, prelude::Color, - render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotInfo, SlotType}, + render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext}, render_resource::*, 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::() .init_resource::>() .init_resource::>() - .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 { @@ -86,12 +83,6 @@ impl Plugin for BloomPlugin { .get_sub_graph_mut(crate::core_3d::graph::NAME) .unwrap(); draw_3d_graph.add_node(core_3d::graph::node::BLOOM, bloom_node); - draw_3d_graph.add_slot_edge( - draw_3d_graph.input_node().id, - crate::core_3d::graph::input::VIEW_ENTITY, - core_3d::graph::node::BLOOM, - BloomNode::IN_VIEW, - ); // MAIN_PASS -> BLOOM -> TONEMAPPING draw_3d_graph.add_node_edge( crate::core_3d::graph::node::MAIN_PASS, @@ -111,12 +102,6 @@ impl Plugin for BloomPlugin { .get_sub_graph_mut(crate::core_2d::graph::NAME) .unwrap(); draw_2d_graph.add_node(core_2d::graph::node::BLOOM, bloom_node); - draw_2d_graph.add_slot_edge( - draw_2d_graph.input_node().id, - crate::core_2d::graph::input::VIEW_ENTITY, - core_2d::graph::node::BLOOM, - BloomNode::IN_VIEW, - ); // MAIN_PASS -> BLOOM -> TONEMAPPING draw_2d_graph.add_node_edge( crate::core_2d::graph::node::MAIN_PASS, @@ -144,8 +129,6 @@ pub struct BloomNode { } impl BloomNode { - pub const IN_VIEW: &'static str = "view"; - pub fn new(world: &mut World) -> Self { Self { view_query: QueryState::new(world), @@ -154,10 +137,6 @@ impl BloomNode { } impl Node for BloomNode { - fn input(&self) -> Vec { - vec![SlotInfo::new(Self::IN_VIEW, SlotType::Entity)] - } - fn update(&mut self, world: &mut World) { self.view_query.update_archetypes(world); } @@ -177,7 +156,7 @@ impl Node for BloomNode { let downsampling_pipeline_res = world.resource::(); let pipeline_cache = world.resource::(); let uniforms = world.resource::>(); - let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + let view_entity = graph.view_entity(); let Ok(( camera, view_target, diff --git a/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs b/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs index b5660c4c0aa58..dd40ffdecbfb3 100644 --- a/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs +++ b/crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs @@ -5,7 +5,7 @@ use crate::{ use bevy_ecs::prelude::*; use bevy_render::{ camera::ExtractedCamera, - render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, + render_graph::{Node, NodeRunError, RenderGraphContext}, render_phase::RenderPhase, render_resource::{LoadOp, Operations, RenderPassDescriptor}, renderer::RenderContext, @@ -27,8 +27,6 @@ pub struct MainPass2dNode { } impl MainPass2dNode { - pub const IN_VIEW: &'static str = "view"; - pub fn new(world: &mut World) -> Self { Self { query: world.query_filtered(), @@ -37,10 +35,6 @@ impl MainPass2dNode { } impl Node for MainPass2dNode { - fn input(&self) -> Vec { - vec![SlotInfo::new(MainPass2dNode::IN_VIEW, SlotType::Entity)] - } - fn update(&mut self, world: &mut World) { self.query.update_archetypes(world); } @@ -51,7 +45,7 @@ impl Node for MainPass2dNode { render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { - let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + let view_entity = graph.view_entity(); let (camera, transparent_phase, target, camera_2d) = if let Ok(result) = self.query.get_manual(world, view_entity) { result diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index a94da4d0b3651..5f866715fbb57 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -20,18 +20,18 @@ 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, extract_component::ExtractComponentPlugin, - render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType}, + render_graph::{EmptyNode, RenderGraph}, render_phase::{ batch_phase_system, sort_phase_system, BatchedPhaseItem, CachedRenderPipelinePhaseItem, 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::>() - .add_systems(( - extract_core_2d_camera_phases.in_schedule(ExtractSchedule), - sort_phase_system::.in_set(RenderSet::PhaseSort), - batch_phase_system:: - .after(sort_phase_system::) - .in_set(RenderSet::PhaseSort), - )); + .add_systems(ExtractSchedule, extract_core_2d_camera_phases) + .add_systems( + Render, + ( + sort_phase_system::.in_set(RenderSet::PhaseSort), + batch_phase_system:: + .after(sort_phase_system::) + .in_set(RenderSet::PhaseSort), + ), + ); let pass_node_2d = MainPass2dNode::new(&mut render_app.world); let tonemapping = TonemappingNode::new(&mut render_app.world); @@ -70,28 +73,6 @@ impl Plugin for Core2dPlugin { draw_2d_graph.add_node(graph::node::TONEMAPPING, tonemapping); draw_2d_graph.add_node(graph::node::END_MAIN_PASS_POST_PROCESSING, EmptyNode); draw_2d_graph.add_node(graph::node::UPSCALING, upscaling); - let input_node_id = draw_2d_graph.set_input(vec![SlotInfo::new( - graph::input::VIEW_ENTITY, - SlotType::Entity, - )]); - draw_2d_graph.add_slot_edge( - input_node_id, - graph::input::VIEW_ENTITY, - graph::node::MAIN_PASS, - MainPass2dNode::IN_VIEW, - ); - draw_2d_graph.add_slot_edge( - input_node_id, - graph::input::VIEW_ENTITY, - graph::node::TONEMAPPING, - TonemappingNode::IN_VIEW, - ); - draw_2d_graph.add_slot_edge( - input_node_id, - graph::input::VIEW_ENTITY, - graph::node::UPSCALING, - UpscalingNode::IN_VIEW, - ); draw_2d_graph.add_node_edge(graph::node::MAIN_PASS, graph::node::TONEMAPPING); draw_2d_graph.add_node_edge( graph::node::TONEMAPPING, diff --git a/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs index 5003fbfd538f1..7bfd88bf6bb33 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs @@ -6,7 +6,7 @@ use crate::{ use bevy_ecs::prelude::*; use bevy_render::{ camera::ExtractedCamera, - render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, + render_graph::{Node, NodeRunError, RenderGraphContext}, render_phase::RenderPhase, render_resource::{LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor}, renderer::RenderContext, @@ -35,8 +35,6 @@ pub struct MainPass3dNode { } impl MainPass3dNode { - pub const IN_VIEW: &'static str = "view"; - pub fn new(world: &mut World) -> Self { Self { query: world.query_filtered(), @@ -45,10 +43,6 @@ impl MainPass3dNode { } impl Node for MainPass3dNode { - fn input(&self) -> Vec { - vec![SlotInfo::new(MainPass3dNode::IN_VIEW, SlotType::Entity)] - } - fn update(&mut self, world: &mut World) { self.query.update_archetypes(world); } @@ -59,7 +53,7 @@ impl Node for MainPass3dNode { render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { - let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + let view_entity = graph.view_entity(); let Ok(( camera, opaque_phase, diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 166f6765e0ea2..4bd6430287867 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -23,13 +23,13 @@ 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}, extract_component::ExtractComponentPlugin, prelude::Msaa, - render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType}, + render_graph::{EmptyNode, RenderGraph}, render_phase::{ sort_phase_system, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, RenderPhase, @@ -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::>() .init_resource::>() .init_resource::>() - .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::.in_set(RenderSet::PhaseSort), - sort_phase_system::.in_set(RenderSet::PhaseSort), - sort_phase_system::.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::.in_set(RenderSet::PhaseSort), + sort_phase_system::.in_set(RenderSet::PhaseSort), + sort_phase_system::.in_set(RenderSet::PhaseSort), + ), + ); let prepass_node = PrepassNode::new(&mut render_app.world); let pass_node_3d = MainPass3dNode::new(&mut render_app.world); @@ -91,34 +94,6 @@ impl Plugin for Core3dPlugin { draw_3d_graph.add_node(graph::node::END_MAIN_PASS_POST_PROCESSING, EmptyNode); draw_3d_graph.add_node(graph::node::UPSCALING, upscaling); - let input_node_id = draw_3d_graph.set_input(vec![SlotInfo::new( - graph::input::VIEW_ENTITY, - SlotType::Entity, - )]); - draw_3d_graph.add_slot_edge( - input_node_id, - graph::input::VIEW_ENTITY, - graph::node::PREPASS, - PrepassNode::IN_VIEW, - ); - draw_3d_graph.add_slot_edge( - input_node_id, - graph::input::VIEW_ENTITY, - graph::node::MAIN_PASS, - MainPass3dNode::IN_VIEW, - ); - draw_3d_graph.add_slot_edge( - input_node_id, - graph::input::VIEW_ENTITY, - graph::node::TONEMAPPING, - TonemappingNode::IN_VIEW, - ); - draw_3d_graph.add_slot_edge( - input_node_id, - graph::input::VIEW_ENTITY, - graph::node::UPSCALING, - UpscalingNode::IN_VIEW, - ); draw_3d_graph.add_node_edge(graph::node::PREPASS, graph::node::MAIN_PASS); draw_3d_graph.add_node_edge(graph::node::MAIN_PASS, graph::node::TONEMAPPING); draw_3d_graph.add_node_edge( diff --git a/crates/bevy_core_pipeline/src/fxaa/mod.rs b/crates/bevy_core_pipeline/src/fxaa/mod.rs index 0892299f24fc8..6c4181d1f4350 100644 --- a/crates/bevy_core_pipeline/src/fxaa/mod.rs +++ b/crates/bevy_core_pipeline/src/fxaa/mod.rs @@ -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::() .init_resource::>() - .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); @@ -99,13 +99,6 @@ impl Plugin for FxaaPlugin { graph.add_node(core_3d::graph::node::FXAA, fxaa_node); - graph.add_slot_edge( - graph.input_node().id, - core_3d::graph::input::VIEW_ENTITY, - core_3d::graph::node::FXAA, - FxaaNode::IN_VIEW, - ); - graph.add_node_edge( core_3d::graph::node::TONEMAPPING, core_3d::graph::node::FXAA, @@ -122,13 +115,6 @@ impl Plugin for FxaaPlugin { graph.add_node(core_2d::graph::node::FXAA, fxaa_node); - graph.add_slot_edge( - graph.input_node().id, - core_2d::graph::input::VIEW_ENTITY, - core_2d::graph::node::FXAA, - FxaaNode::IN_VIEW, - ); - graph.add_node_edge( core_2d::graph::node::TONEMAPPING, core_2d::graph::node::FXAA, diff --git a/crates/bevy_core_pipeline/src/fxaa/node.rs b/crates/bevy_core_pipeline/src/fxaa/node.rs index 5050e3c4b3920..71d66ca27a34f 100644 --- a/crates/bevy_core_pipeline/src/fxaa/node.rs +++ b/crates/bevy_core_pipeline/src/fxaa/node.rs @@ -4,7 +4,7 @@ use crate::fxaa::{CameraFxaaPipeline, Fxaa, FxaaPipeline}; use bevy_ecs::prelude::*; use bevy_ecs::query::QueryState; use bevy_render::{ - render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, + render_graph::{Node, NodeRunError, RenderGraphContext}, render_resource::{ BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, FilterMode, Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, SamplerDescriptor, @@ -28,8 +28,6 @@ pub struct FxaaNode { } impl FxaaNode { - pub const IN_VIEW: &'static str = "view"; - pub fn new(world: &mut World) -> Self { Self { query: QueryState::new(world), @@ -39,10 +37,6 @@ impl FxaaNode { } impl Node for FxaaNode { - fn input(&self) -> Vec { - vec![SlotInfo::new(FxaaNode::IN_VIEW, SlotType::Entity)] - } - fn update(&mut self, world: &mut World) { self.query.update_archetypes(world); } @@ -53,7 +47,7 @@ impl Node for FxaaNode { render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { - let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + let view_entity = graph.view_entity(); let pipeline_cache = world.resource::(); let fxaa_pipeline = world.resource::(); diff --git a/crates/bevy_core_pipeline/src/msaa_writeback.rs b/crates/bevy_core_pipeline/src/msaa_writeback.rs index 2f8122d193d4c..43401ec5682a8 100644 --- a/crates/bevy_core_pipeline/src/msaa_writeback.rs +++ b/crates/bevy_core_pipeline/src/msaa_writeback.rs @@ -3,10 +3,10 @@ use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; use bevy_render::{ camera::ExtractedCamera, - render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotInfo, SlotType}, + render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext}, renderer::RenderContext, view::{Msaa, ViewTarget}, - RenderSet, + Render, RenderSet, }; use bevy_render::{render_resource::*, RenderApp}; @@ -20,12 +20,14 @@ 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::(); if let Some(core_2d) = graph.get_sub_graph_mut(crate::core_2d::graph::NAME) { - let input_node = core_2d.input_node().id; core_2d.add_node( crate::core_2d::graph::node::MSAA_WRITEBACK, msaa_writeback_2d, @@ -34,16 +36,9 @@ impl Plugin for MsaaWritebackPlugin { crate::core_2d::graph::node::MSAA_WRITEBACK, crate::core_2d::graph::node::MAIN_PASS, ); - core_2d.add_slot_edge( - input_node, - crate::core_2d::graph::input::VIEW_ENTITY, - crate::core_2d::graph::node::MSAA_WRITEBACK, - MsaaWritebackNode::IN_VIEW, - ); } if let Some(core_3d) = graph.get_sub_graph_mut(crate::core_3d::graph::NAME) { - let input_node = core_3d.input_node().id; core_3d.add_node( crate::core_3d::graph::node::MSAA_WRITEBACK, msaa_writeback_3d, @@ -52,12 +47,6 @@ impl Plugin for MsaaWritebackPlugin { crate::core_3d::graph::node::MSAA_WRITEBACK, crate::core_3d::graph::node::MAIN_PASS, ); - core_3d.add_slot_edge( - input_node, - crate::core_3d::graph::input::VIEW_ENTITY, - crate::core_3d::graph::node::MSAA_WRITEBACK, - MsaaWritebackNode::IN_VIEW, - ); } } } @@ -67,8 +56,6 @@ pub struct MsaaWritebackNode { } impl MsaaWritebackNode { - pub const IN_VIEW: &'static str = "view"; - pub fn new(world: &mut World) -> Self { Self { cameras: world.query(), @@ -77,9 +64,6 @@ impl MsaaWritebackNode { } impl Node for MsaaWritebackNode { - fn input(&self) -> Vec { - vec![SlotInfo::new(Self::IN_VIEW, SlotType::Entity)] - } fn update(&mut self, world: &mut World) { self.cameras.update_archetypes(world); } @@ -89,7 +73,7 @@ impl Node for MsaaWritebackNode { render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { - let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + let view_entity = graph.view_entity(); if let Ok((target, blit_pipeline_id)) = self.cameras.get_manual(world, view_entity) { let blit_pipeline = world.resource::(); let pipeline_cache = world.resource::(); diff --git a/crates/bevy_core_pipeline/src/prepass/node.rs b/crates/bevy_core_pipeline/src/prepass/node.rs index 2687b925c00f5..0e4cb17f846e0 100644 --- a/crates/bevy_core_pipeline/src/prepass/node.rs +++ b/crates/bevy_core_pipeline/src/prepass/node.rs @@ -3,7 +3,7 @@ use bevy_ecs::query::QueryState; use bevy_render::{ camera::ExtractedCamera, prelude::Color, - render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, + render_graph::{Node, NodeRunError, RenderGraphContext}, render_phase::RenderPhase, render_resource::{ LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment, @@ -34,8 +34,6 @@ pub struct PrepassNode { } impl PrepassNode { - pub const IN_VIEW: &'static str = "view"; - pub fn new(world: &mut World) -> Self { Self { main_view_query: QueryState::new(world), @@ -44,10 +42,6 @@ impl PrepassNode { } impl Node for PrepassNode { - fn input(&self) -> Vec { - vec![SlotInfo::new(Self::IN_VIEW, SlotType::Entity)] - } - fn update(&mut self, world: &mut World) { self.main_view_query.update_archetypes(world); } @@ -58,7 +52,7 @@ impl Node for PrepassNode { render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { - let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + let view_entity = graph.view_entity(); let Ok(( camera, opaque_prepass_phase, diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index d6c2f3a062e53..31ecd12177d93 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -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::() .init_resource::>() - .add_system(queue_view_tonemapping_pipelines.in_set(RenderSet::Queue)); + .add_systems( + Render, + queue_view_tonemapping_pipelines.in_set(RenderSet::Queue), + ); } } } diff --git a/crates/bevy_core_pipeline/src/tonemapping/node.rs b/crates/bevy_core_pipeline/src/tonemapping/node.rs index 357822fc73c6e..eed9013ba375e 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/node.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/node.rs @@ -6,7 +6,7 @@ use bevy_ecs::prelude::*; use bevy_ecs::query::QueryState; use bevy_render::{ render_asset::RenderAssets, - render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, + render_graph::{Node, NodeRunError, RenderGraphContext}, render_resource::{ BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, BufferId, LoadOp, Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, @@ -34,8 +34,6 @@ pub struct TonemappingNode { } impl TonemappingNode { - pub const IN_VIEW: &'static str = "view"; - pub fn new(world: &mut World) -> Self { Self { query: QueryState::new(world), @@ -46,10 +44,6 @@ impl TonemappingNode { } impl Node for TonemappingNode { - fn input(&self) -> Vec { - vec![SlotInfo::new(TonemappingNode::IN_VIEW, SlotType::Entity)] - } - fn update(&mut self, world: &mut World) { self.query.update_archetypes(world); } @@ -60,7 +54,7 @@ impl Node for TonemappingNode { render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { - let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + let view_entity = graph.view_entity(); let pipeline_cache = world.resource::(); let tonemapping_pipeline = world.resource::(); let gpu_images = world.get_resource::>().unwrap(); diff --git a/crates/bevy_core_pipeline/src/upscaling/mod.rs b/crates/bevy_core_pipeline/src/upscaling/mod.rs index 441f9f77758c5..f3594397d5120 100644 --- a/crates/bevy_core_pipeline/src/upscaling/mod.rs +++ b/crates/bevy_core_pipeline/src/upscaling/mod.rs @@ -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), + ); } } } diff --git a/crates/bevy_core_pipeline/src/upscaling/node.rs b/crates/bevy_core_pipeline/src/upscaling/node.rs index 8e66f1eb07f06..286b809620318 100644 --- a/crates/bevy_core_pipeline/src/upscaling/node.rs +++ b/crates/bevy_core_pipeline/src/upscaling/node.rs @@ -3,7 +3,7 @@ use bevy_ecs::prelude::*; use bevy_ecs::query::QueryState; use bevy_render::{ camera::{CameraOutputMode, ExtractedCamera}, - render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, + render_graph::{Node, NodeRunError, RenderGraphContext}, render_resource::{ BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, LoadOp, Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, SamplerDescriptor, @@ -27,8 +27,6 @@ pub struct UpscalingNode { } impl UpscalingNode { - pub const IN_VIEW: &'static str = "view"; - pub fn new(world: &mut World) -> Self { Self { query: QueryState::new(world), @@ -38,10 +36,6 @@ impl UpscalingNode { } impl Node for UpscalingNode { - fn input(&self) -> Vec { - vec![SlotInfo::new(UpscalingNode::IN_VIEW, SlotType::Entity)] - } - fn update(&mut self, world: &mut World) { self.query.update_archetypes(world); } @@ -52,7 +46,7 @@ impl Node for UpscalingNode { render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { - let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + let view_entity = graph.view_entity(); let pipeline_cache = world.get_resource::().unwrap(); let blit_pipeline = world.get_resource::().unwrap(); diff --git a/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs index fd6b21117c80d..5c4415c09b621 100644 --- a/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs @@ -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); } } diff --git a/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs index 8e26025bf2cbc..4dbc3c4cfa92d 100644 --- a/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs @@ -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); } } diff --git a/crates/bevy_diagnostic/src/lib.rs b/crates/bevy_diagnostic/src/lib.rs index b2d127f115600..854f6e74cd854 100644 --- a/crates/bevy_diagnostic/src/lib.rs +++ b/crates/bevy_diagnostic/src/lib.rs @@ -17,8 +17,10 @@ pub struct DiagnosticsPlugin; impl Plugin for DiagnosticsPlugin { fn build(&self, app: &mut App) { - app.init_resource::() - .add_startup_system(system_information_diagnostics_plugin::internal::log_system_info); + app.init_resource::().add_systems( + Startup, + system_information_diagnostics_plugin::internal::log_system_info, + ); } } diff --git a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs index dd80c8c8f2024..65f212dcc60ca 100644 --- a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs @@ -37,9 +37,9 @@ impl Plugin for LogDiagnosticsPlugin { }); if self.debug { - app.add_system(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); } } } diff --git a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs index 096c9b13a6925..b33cba5ca468c 100644 --- a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs @@ -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); } } diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 9112683c22e6c..cfcea53b3644c 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -27,6 +27,7 @@ fixedbitset = "0.4.2" rustc-hash = "1.1" downcast-rs = "1.2" serde = { version = "1", features = ["derive"] } +thiserror = "1.0" [dev-dependencies] rand = "0.8" diff --git a/crates/bevy_ecs/README.md b/crates/bevy_ecs/README.md index ee9126e1b5fb7..457f386e89fa4 100644 --- a/crates/bevy_ecs/README.md +++ b/crates/bevy_ecs/README.md @@ -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); diff --git a/crates/bevy_ecs/examples/change_detection.rs b/crates/bevy_ecs/examples/change_detection.rs index 0abbf487a0678..8bfa18d735f6d 100644 --- a/crates/bevy_ecs/examples/change_detection.rs +++ b/crates/bevy_ecs/examples/change_detection.rs @@ -1,4 +1,4 @@ -use bevy_ecs::{prelude::*, schedule::IntoSystemConfig}; +use bevy_ecs::prelude::*; use rand::Rng; use std::ops::Deref; diff --git a/crates/bevy_ecs/examples/events.rs b/crates/bevy_ecs/examples/events.rs index 2dae9475cf9e8..27bd13b5f6f2e 100644 --- a/crates/bevy_ecs/examples/events.rs +++ b/crates/bevy_ecs/examples/events.rs @@ -16,7 +16,7 @@ fn main() { #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] pub struct FlushEvents; - schedule.add_system(Events::::update_system.in_set(FlushEvents)); + schedule.add_systems(Events::::update_system.in_set(FlushEvents)); // Add systems sending and receiving events after the events are flushed. schedule.add_systems(( diff --git a/crates/bevy_ecs/macros/src/fetch.rs b/crates/bevy_ecs/macros/src/fetch.rs index 8bb5597f0d292..a72b4d10f3094 100644 --- a/crates/bevy_ecs/macros/src/fetch.rs +++ b/crates/bevy_ecs/macros/src/fetch.rs @@ -1,9 +1,10 @@ +use bevy_macro_utils::ensure_no_collision; use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; use quote::{quote, ToTokens}; use syn::{ parse::{Parse, ParseStream}, - parse_quote, + parse_macro_input, parse_quote, punctuated::Punctuated, Attribute, Data, DataStruct, DeriveInput, Field, Fields, }; @@ -25,7 +26,10 @@ mod field_attr_keywords { pub static WORLD_QUERY_ATTRIBUTE_NAME: &str = "world_query"; -pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { +pub fn derive_world_query_impl(input: TokenStream) -> TokenStream { + let tokens = input.clone(); + + let ast = parse_macro_input!(input as DeriveInput); let visibility = ast.vis; let mut fetch_struct_attributes = FetchStructAttributes::default(); @@ -104,13 +108,18 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { }; let fetch_struct_name = Ident::new(&format!("{struct_name}Fetch"), Span::call_site()); + let fetch_struct_name = ensure_no_collision(fetch_struct_name, tokens.clone()); let read_only_fetch_struct_name = if fetch_struct_attributes.is_mutable { - Ident::new(&format!("{struct_name}ReadOnlyFetch"), Span::call_site()) + let new_ident = Ident::new(&format!("{struct_name}ReadOnlyFetch"), Span::call_site()); + ensure_no_collision(new_ident, tokens.clone()) } else { fetch_struct_name.clone() }; + // Generate a name for the state struct that doesn't conflict + // with the struct definition. let state_struct_name = Ident::new(&format!("{struct_name}State"), Span::call_site()); + let state_struct_name = ensure_no_collision(state_struct_name, tokens); let fields = match &ast.data { Data::Struct(DataStruct { @@ -339,7 +348,7 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { #[doc = "`]."] #[automatically_derived] #visibility struct #read_only_struct_name #user_impl_generics #user_where_clauses { - #( #field_idents: #read_only_field_types, )* + #( #field_visibilities #field_idents: #read_only_field_types, )* #(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)* } diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index dbafe0d9e3ead..3866cea31595f 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -6,7 +6,9 @@ mod set; mod states; use crate::{fetch::derive_world_query_impl, set::derive_set}; -use bevy_macro_utils::{derive_boxed_label, get_named_struct_fields, BevyManifest}; +use bevy_macro_utils::{ + derive_boxed_label, ensure_no_collision, get_named_struct_fields, BevyManifest, +}; use proc_macro::TokenStream; use proc_macro2::Span; use quote::{format_ident, quote}; @@ -126,7 +128,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { #(#field_from_components)* } } + } + impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause { #[allow(unused_variables)] #[inline] fn get_components( @@ -258,6 +262,7 @@ static SYSTEM_PARAM_ATTRIBUTE_NAME: &str = "system_param"; /// Implement `SystemParam` to use a struct as a parameter in a system #[proc_macro_derive(SystemParam, attributes(system_param))] pub fn derive_system_param(input: TokenStream) -> TokenStream { + let token_stream = input.clone(); let ast = parse_macro_input!(input as DeriveInput); let syn::Data::Struct(syn::DataStruct { fields: field_definitions, ..}) = ast.data else { return syn::Error::new(ast.span(), "Invalid `SystemParam` type: expected a `struct`") @@ -392,6 +397,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { let struct_name = &ast.ident; let state_struct_visibility = &ast.vis; + let state_struct_name = ensure_no_collision(format_ident!("FetchState"), token_stream); TokenStream::from(quote! { // We define the FetchState struct in an anonymous scope to avoid polluting the user namespace. @@ -399,7 +405,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { // as SystemParam>::State const _: () = { #[doc(hidden)] - #state_struct_visibility struct FetchState <'w, 's, #(#lifetimeless_generics,)*> + #state_struct_visibility struct #state_struct_name <'w, 's, #(#lifetimeless_generics,)*> #where_clause { state: (#(<#tuple_types as #path::system::SystemParam>::State,)*), marker: std::marker::PhantomData<( @@ -409,11 +415,11 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { } unsafe impl<'w, 's, #punctuated_generics> #path::system::SystemParam for #struct_name #ty_generics #where_clause { - type State = FetchState<'static, 'static, #punctuated_generic_idents>; + type State = #state_struct_name<'static, 'static, #punctuated_generic_idents>; type Item<'_w, '_s> = #struct_name <#(#shadowed_lifetimes,)* #punctuated_generic_idents>; fn init_state(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self::State { - FetchState { + #state_struct_name { state: <(#(#tuple_types,)*) as #path::system::SystemParam>::init_state(world, system_meta), marker: std::marker::PhantomData, } @@ -452,8 +458,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { /// Implement `WorldQuery` to use a struct as a parameter in a query #[proc_macro_derive(WorldQuery, attributes(world_query))] pub fn derive_world_query(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as DeriveInput); - derive_world_query_impl(ast) + derive_world_query_impl(input) } /// Derive macro generating an impl of the trait `ScheduleLabel`. @@ -469,7 +474,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(); diff --git a/crates/bevy_ecs/macros/src/set.rs b/crates/bevy_ecs/macros/src/set.rs index 054735c858653..66bfb601ecf35 100644 --- a/crates/bevy_ecs/macros/src/set.rs +++ b/crates/bevy_ecs/macros/src/set.rs @@ -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::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 { std::boxed::Box::new(std::clone::Clone::clone(self)) } } - - #marker_impl }) .into() } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 66fe6cdbcfb8b..c86ef4bad1fde 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -3,7 +3,7 @@ //! This module contains the [`Bundle`] trait and some other helper types. pub use bevy_ecs_macros::Bundle; -use bevy_utils::HashSet; +use bevy_utils::{HashMap, HashSet}; use crate::{ archetype::{ @@ -12,6 +12,7 @@ use crate::{ }, component::{Component, ComponentId, ComponentStorage, Components, StorageType, Tick}, entity::{Entities, Entity, EntityLocation}, + query::DebugCheckedUnwrap, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, TypeIdMap, }; @@ -135,10 +136,10 @@ use std::any::TypeId; /// [`Query`]: crate::system::Query // Some safety points: // - [`Bundle::component_ids`] must return the [`ComponentId`] for each component type in the -// bundle, in the _exact_ order that [`Bundle::get_components`] is called. +// bundle, in the _exact_ order that [`DynamicBundle::get_components`] is called. // - [`Bundle::from_components`] must call `func` exactly once for each [`ComponentId`] returned by // [`Bundle::component_ids`]. -pub unsafe trait Bundle: Send + Sync + 'static { +pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static { /// Gets this [`Bundle`]'s component ids, in the order of this bundle's [`Component`]s #[doc(hidden)] fn component_ids( @@ -159,7 +160,10 @@ pub unsafe trait Bundle: Send + Sync + 'static { // Ensure that the `OwningPtr` is used correctly F: for<'a> FnMut(&'a mut T) -> OwningPtr<'a>, Self: Sized; +} +/// The parts from [`Bundle`] that don't require statically knowing the components of the bundle. +pub trait DynamicBundle { // SAFETY: // The `StorageType` argument passed into [`Bundle::get_components`] must be correct for the // component being fetched. @@ -192,7 +196,9 @@ unsafe impl Bundle for C { // Safety: The id given in `component_ids` is for `Self` func(ctx).read() } +} +impl DynamicBundle for C { #[inline] fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) { OwningPtr::make(self, |ptr| func(C::Storage::STORAGE_TYPE, ptr)); @@ -203,7 +209,7 @@ macro_rules! tuple_impl { ($($name: ident),*) => { // SAFETY: // - `Bundle::component_ids` calls `ids` for each component type in the - // bundle, in the exact order that `Bundle::get_components` is called. + // bundle, in the exact order that `DynamicBundle::get_components` is called. // - `Bundle::from_components` calls `func` exactly once for each `ComponentId` returned by `Bundle::component_ids`. // - `Bundle::get_components` is called exactly once for each member. Relies on the above implementation to pass the correct // `StorageType` into the callback. @@ -223,7 +229,9 @@ macro_rules! tuple_impl { // https://doc.rust-lang.org/reference/expressions.html#evaluation-order-of-operands ($(<$name as Bundle>::from_components(ctx, func),)*) } + } + impl<$($name: Bundle),*> DynamicBundle for ($($name,)*) { #[allow(unused_variables, unused_mut)] #[inline(always)] fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) { @@ -261,13 +269,62 @@ impl SparseSetIndex for BundleId { } pub struct BundleInfo { - pub(crate) id: BundleId, - pub(crate) component_ids: Vec, + id: BundleId, + // SAFETY: Every ID in this list must be valid within the World that owns the BundleInfo, + // must have its storage initialized (i.e. columns created in tables, sparse set created), + // and must be in the same order as the source bundle type writes its components in. + component_ids: Vec, } impl BundleInfo { + /// Create a new [`BundleInfo`]. + /// + /// # Safety + /// + // Every ID in `component_ids` must be valid within the World that owns the BundleInfo, + // must have its storage initialized (i.e. columns created in tables, sparse set created), + // and must be in the same order as the source bundle type writes its components in. + unsafe fn new( + bundle_type_name: &'static str, + components: &Components, + component_ids: Vec, + id: BundleId, + ) -> BundleInfo { + let mut deduped = component_ids.clone(); + deduped.sort(); + deduped.dedup(); + + if deduped.len() != component_ids.len() { + // TODO: Replace with `Vec::partition_dedup` once https://github.com/rust-lang/rust/issues/54279 is stabilized + let mut seen = HashSet::new(); + let mut dups = Vec::new(); + for id in component_ids { + if !seen.insert(id) { + dups.push(id); + } + } + + let names = dups + .into_iter() + .map(|id| { + // SAFETY: the caller ensures component_id is valid. + unsafe { components.get_info_unchecked(id).name() } + }) + .collect::>() + .join(", "); + + panic!("Bundle {bundle_type_name} has duplicate components: {names}"); + } + + // SAFETY: The caller ensures that component_ids: + // - is valid for the associated world + // - has had its storage initialized + // - is in the same order as the source bundle type + BundleInfo { id, component_ids } + } + #[inline] - pub fn id(&self) -> BundleId { + pub const fn id(&self) -> BundleId { self.id } @@ -376,7 +433,7 @@ impl BundleInfo { /// `entity`, `bundle` must match this [`BundleInfo`]'s type #[inline] #[allow(clippy::too_many_arguments)] - unsafe fn write_components( + unsafe fn write_components( &self, table: &mut Table, sparse_sets: &mut SparseSets, @@ -393,7 +450,10 @@ impl BundleInfo { let component_id = *self.component_ids.get_unchecked(bundle_component); match storage_type { StorageType::Table => { - let column = table.get_column_mut(component_id).unwrap(); + let column = + // SAFETY: If component_id is in self.component_ids, BundleInfo::new requires that + // the target table contains the component. + unsafe { table.get_column_mut(component_id).debug_checked_unwrap() }; // SAFETY: bundle_component is a valid index for this bundle match bundle_component_status.get_status(bundle_component) { ComponentStatus::Added => { @@ -405,11 +465,11 @@ impl BundleInfo { } } StorageType::SparseSet => { - sparse_sets.get_mut(component_id).unwrap().insert( - entity, - component_ptr, - change_tick, - ); + let sparse_set = + // SAFETY: If component_id is in self.component_ids, BundleInfo::new requires that + // a sparse set exists for the component. + unsafe { sparse_sets.get_mut(component_id).debug_checked_unwrap() }; + sparse_set.insert(entity, component_ptr, change_tick); } } bundle_component += 1; @@ -527,7 +587,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> { /// `entity` must currently exist in the source archetype for this inserter. `archetype_row` /// must be `entity`'s location in the archetype. `T` must match this [`BundleInfo`]'s type #[inline] - pub unsafe fn insert( + pub unsafe fn insert( &mut self, entity: Entity, location: EntityLocation, @@ -536,11 +596,13 @@ impl<'a, 'b> BundleInserter<'a, 'b> { match &mut self.result { InsertBundleResult::SameArchetype => { // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) - let add_bundle = self - .archetype - .edges() - .get_add_bundle_internal(self.bundle_info.id) - .unwrap(); + // SAFETY: The edge is assured to be initialized when creating the BundleInserter + let add_bundle = unsafe { + self.archetype + .edges() + .get_add_bundle_internal(self.bundle_info.id) + .debug_checked_unwrap() + }; self.bundle_info.write_components( self.table, self.sparse_sets, @@ -555,7 +617,9 @@ impl<'a, 'b> BundleInserter<'a, 'b> { InsertBundleResult::NewArchetypeSameTable { new_archetype } => { let result = self.archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { - let swapped_location = self.entities.get(swapped_entity).unwrap(); + let swapped_location = + // SAFETY: If the swap was successful, swapped_entity must be valid. + unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; self.entities.set( swapped_entity.index(), EntityLocation { @@ -570,11 +634,13 @@ impl<'a, 'b> BundleInserter<'a, 'b> { self.entities.set(entity.index(), new_location); // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) - let add_bundle = self - .archetype - .edges() - .get_add_bundle_internal(self.bundle_info.id) - .unwrap(); + // SAFETY: The edge is assured to be initialized when creating the BundleInserter + let add_bundle = unsafe { + self.archetype + .edges() + .get_add_bundle_internal(self.bundle_info.id) + .debug_checked_unwrap() + }; self.bundle_info.write_components( self.table, self.sparse_sets, @@ -592,7 +658,9 @@ impl<'a, 'b> BundleInserter<'a, 'b> { } => { let result = self.archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { - let swapped_location = self.entities.get(swapped_entity).unwrap(); + let swapped_location = + // SAFETY: If the swap was successful, swapped_entity must be valid. + unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; self.entities.set( swapped_entity.index(), EntityLocation { @@ -613,7 +681,9 @@ impl<'a, 'b> BundleInserter<'a, 'b> { // if an entity was moved into this entity's table spot, update its table row if let Some(swapped_entity) = move_result.swapped_entity { - let swapped_location = self.entities.get(swapped_entity).unwrap(); + let swapped_location = + // SAFETY: If the swap was successful, swapped_entity must be valid. + unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() }; let swapped_archetype = if self.archetype.id() == swapped_location.archetype_id { &mut *self.archetype @@ -640,11 +710,13 @@ impl<'a, 'b> BundleInserter<'a, 'b> { } // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) - let add_bundle = self - .archetype - .edges() - .get_add_bundle_internal(self.bundle_info.id) - .unwrap(); + // SAFETY: The edge is assured to be initialized when creating the BundleInserter + let add_bundle = unsafe { + self.archetype + .edges() + .get_add_bundle_internal(self.bundle_info.id) + .debug_checked_unwrap() + }; self.bundle_info.write_components( new_table, self.sparse_sets, @@ -677,7 +749,7 @@ impl<'a, 'b> BundleSpawner<'a, 'b> { /// # Safety /// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type #[inline] - pub unsafe fn spawn_non_existent( + pub unsafe fn spawn_non_existent( &mut self, entity: Entity, bundle: T, @@ -712,7 +784,12 @@ impl<'a, 'b> BundleSpawner<'a, 'b> { #[derive(Default)] pub struct Bundles { bundle_infos: Vec, + /// Cache static [`BundleId`] bundle_ids: TypeIdMap, + /// Cache dynamic [`BundleId`] with multiple components + dynamic_bundle_ids: HashMap, (BundleId, Vec)>, + /// Cache optimized dynamic [`BundleId`] with single component + dynamic_component_bundle_ids: HashMap, } impl Bundles { @@ -726,6 +803,7 @@ impl Bundles { self.bundle_ids.get(&type_id).cloned() } + /// Initializes a new [`BundleInfo`] for a statically known type. pub(crate) fn init_info<'a, T: Bundle>( &'a mut self, components: &mut Components, @@ -737,50 +815,98 @@ impl Bundles { T::component_ids(components, storages, &mut |id| component_ids.push(id)); let id = BundleId(bundle_infos.len()); let bundle_info = - // SAFETY: T::component_id ensures info was created - unsafe { initialize_bundle(std::any::type_name::(), components, component_ids, id) }; + // SAFETY: T::component_id ensures its: + // - info was created + // - appropriate storage for it has been initialized. + // - was created in the same order as the components in T + unsafe { BundleInfo::new(std::any::type_name::(), components, component_ids, id) }; bundle_infos.push(bundle_info); id }); // SAFETY: index either exists, or was initialized unsafe { self.bundle_infos.get_unchecked(id.0) } } -} -/// # Safety -/// -/// `component_id` must be valid [`ComponentId`]'s -unsafe fn initialize_bundle( - bundle_type_name: &'static str, - components: &Components, - component_ids: Vec, - id: BundleId, -) -> BundleInfo { - let mut deduped = component_ids.clone(); - deduped.sort(); - deduped.dedup(); - - if deduped.len() != component_ids.len() { - // TODO: Replace with `Vec::partition_dedup` once https://github.com/rust-lang/rust/issues/54279 is stabilized - let mut seen = HashSet::new(); - let mut dups = Vec::new(); - for id in component_ids { - if !seen.insert(id) { - dups.push(id); - } - } + /// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`]. + /// + /// # Panics + /// + /// Panics if any of the provided [`ComponentId`]s do not exist in the + /// provided [`Components`]. + pub(crate) fn init_dynamic_info( + &mut self, + components: &mut Components, + component_ids: &[ComponentId], + ) -> (&BundleInfo, &Vec) { + let bundle_infos = &mut self.bundle_infos; + + // Use `raw_entry_mut` to avoid cloning `component_ids` to access `Entry` + let (_, (bundle_id, storage_types)) = self + .dynamic_bundle_ids + .raw_entry_mut() + .from_key(component_ids) + .or_insert_with(|| { + ( + Vec::from(component_ids), + initialize_dynamic_bundle(bundle_infos, components, Vec::from(component_ids)), + ) + }); + + // SAFETY: index either exists, or was initialized + let bundle_info = unsafe { bundle_infos.get_unchecked(bundle_id.0) }; - let names = dups - .into_iter() - .map(|id| { - // SAFETY: component_id exists and is therefore valid - unsafe { components.get_info_unchecked(id).name() } - }) - .collect::>() - .join(", "); + (bundle_info, storage_types) + } + + /// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`] with single component. + /// + /// # Panics + /// + /// Panics if the provided [`ComponentId`] does not exist in the provided [`Components`]. + pub(crate) fn init_component_info( + &mut self, + components: &mut Components, + component_id: ComponentId, + ) -> (&BundleInfo, StorageType) { + let bundle_infos = &mut self.bundle_infos; + let (bundle_id, storage_types) = self + .dynamic_component_bundle_ids + .entry(component_id) + .or_insert_with(|| { + let (id, storage_type) = + initialize_dynamic_bundle(bundle_infos, components, vec![component_id]); + // SAFETY: `storage_type` guaranteed to have length 1 + (id, storage_type[0]) + }); + + // SAFETY: index either exists, or was initialized + let bundle_info = unsafe { bundle_infos.get_unchecked(bundle_id.0) }; - panic!("Bundle {bundle_type_name} has duplicate components: {names}"); + (bundle_info, *storage_types) } +} - BundleInfo { id, component_ids } +/// Asserts that all components are part of [`Components`] +/// and initializes a [`BundleInfo`]. +fn initialize_dynamic_bundle( + bundle_infos: &mut Vec, + components: &Components, + component_ids: Vec, +) -> (BundleId, Vec) { + // Assert component existence + let storage_types = component_ids.iter().map(|&id| { + components.get_info(id).unwrap_or_else(|| { + panic!( + "init_dynamic_info called with component id {id:?} which doesn't exist in this world" + ) + }).storage_type() + }).collect(); + + let id = BundleId(bundle_infos.len()); + let bundle_info = + // SAFETY: `component_ids` are valid as they were just checked + unsafe { BundleInfo::new("", components, component_ids, id) }; + bundle_infos.push(bundle_info); + + (id, storage_types) } diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 1b1afe3151fc6..843e3262a2bf3 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -571,8 +571,7 @@ impl Entities { /// Returns the location of an [`Entity`]. /// Note: for pending entities, returns `Some(EntityLocation::INVALID)`. pub fn get(&self, entity: Entity) -> Option { - if (entity.index as usize) < self.meta.len() { - let meta = &self.meta[entity.index as usize]; + if let Some(meta) = self.meta.get(entity.index as usize) { if meta.generation != entity.generation || meta.location.archetype_id == ArchetypeId::INVALID { diff --git a/crates/bevy_ecs/src/event.rs b/crates/bevy_ecs/src/event.rs index 0bf642917509b..3cdfc19445d57 100644 --- a/crates/bevy_ecs/src/event.rs +++ b/crates/bevy_ecs/src/event.rs @@ -939,7 +939,7 @@ mod tests { world.send_event(TestEvent { i: 4 }); let mut schedule = Schedule::new(); - schedule.add_system(|mut events: EventReader| { + schedule.add_systems(|mut events: EventReader| { let mut iter = events.iter(); assert_eq!(iter.next(), Some(&TestEvent { i: 0 })); diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 1c574befe7065..8107de37e88a4 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -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, diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 3b4a6a076f143..c9a18d1081b78 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1388,3 +1388,65 @@ unsafe impl WorldQuery for NopWorldQuery { /// SAFETY: `NopFetch` never accesses any data unsafe impl ReadOnlyWorldQuery for NopWorldQuery {} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{self as bevy_ecs, system::Query}; + + #[derive(Component)] + pub struct A; + + // Ensures that each field of a `WorldQuery` struct's read-only variant + // has the same visibility as its corresponding mutable field. + #[test] + fn read_only_field_visibility() { + mod private { + use super::*; + + #[derive(WorldQuery)] + #[world_query(mutable)] + pub struct Q { + pub a: &'static mut A, + } + } + + let _ = private::QReadOnly { a: &A }; + + fn my_system(query: Query) { + for q in &query { + let _ = &q.a; + } + } + + crate::system::assert_is_system(my_system); + } + + // Ensures that metadata types generated by the WorldQuery macro + // do not conflict with user-defined types. + // Regression test for https://github.com/bevyengine/bevy/issues/8010. + #[test] + fn world_query_metadata_collision() { + // The metadata types generated would be named `ClientState` and `ClientFetch`, + // but they should rename themselves to avoid conflicts. + #[derive(WorldQuery)] + pub struct Client { + pub state: &'static S, + pub fetch: &'static ClientFetch, + } + + pub trait ClientState: Component {} + + #[derive(Component)] + pub struct ClientFetch; + + #[derive(Component)] + pub struct C; + + impl ClientState for C {} + + fn client_system(_: Query>) {} + + crate::system::assert_is_system(client_system); + } +} diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index 83fe596766395..8b665219b4e2b 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -61,8 +61,9 @@ impl DebugCheckedUnwrap for Option { #[cfg(test)] mod tests { use super::{ReadOnlyWorldQuery, WorldQuery}; - use crate::prelude::{AnyOf, Entity, Or, QueryState, With, Without}; + use crate::prelude::{AnyOf, Changed, Entity, Or, QueryState, With, Without}; use crate::query::{ArchetypeFilter, QueryCombinationIter}; + use crate::schedule::{IntoSystemConfigs, Schedule}; use crate::system::{IntoSystem, Query, System, SystemState}; use crate::{self as bevy_ecs, component::Component, world::World}; use std::any::type_name; @@ -749,4 +750,33 @@ mod tests { let _: [&Foo; 1] = q.many([e]); let _: &Foo = q.single(); } + + // regression test for https://github.com/bevyengine/bevy/pull/8029 + #[test] + fn par_iter_mut_change_detection() { + let mut world = World::new(); + world.spawn((A(1), B(1))); + + fn propagate_system(mut query: Query<(&A, &mut B), Changed>) { + query.par_iter_mut().for_each_mut(|(a, mut b)| { + b.0 = a.0; + }); + } + + fn modify_system(mut query: Query<&mut A>) { + for mut a in &mut query { + a.0 = 2; + } + } + + let mut schedule = Schedule::new(); + schedule.add_systems((propagate_system, modify_system).chain()); + schedule.run(&mut world); + world.clear_trackers(); + schedule.run(&mut world); + world.clear_trackers(); + + let values = world.query::<&B>().iter(&world).collect::>(); + assert_eq!(values, vec![&B(2)]); + } } diff --git a/crates/bevy_ecs/src/query/par_iter.rs b/crates/bevy_ecs/src/query/par_iter.rs index 72744a8efa7f4..4d3393c8f0ceb 100644 --- a/crates/bevy_ecs/src/query/par_iter.rs +++ b/crates/bevy_ecs/src/query/par_iter.rs @@ -1,4 +1,4 @@ -use crate::world::World; +use crate::{component::Tick, world::World}; use bevy_tasks::ComputeTaskPool; use std::ops::Range; @@ -81,6 +81,8 @@ impl BatchingStrategy { pub struct QueryParIter<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> { pub(crate) world: &'w World, pub(crate) state: &'s QueryState, + pub(crate) last_run: Tick, + pub(crate) this_run: Tick, pub(crate) batching_strategy: BatchingStrategy, } @@ -148,12 +150,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> { ) { let thread_count = ComputeTaskPool::get().thread_num(); if thread_count <= 1 { - self.state.for_each_unchecked_manual( - self.world, - func, - self.world.last_change_tick(), - self.world.read_change_tick(), - ); + self.state + .for_each_unchecked_manual(self.world, func, self.last_run, self.this_run); } else { // Need a batch size of at least 1. let batch_size = self.get_batch_size(thread_count).max(1); @@ -161,8 +159,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> { self.world, batch_size, func, - self.world.last_change_tick(), - self.world.read_change_tick(), + self.last_run, + self.this_run, ); } } diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index d85c2965dec27..65774d40a99cd 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -820,6 +820,8 @@ impl QueryState { QueryParIter { world, state: self, + last_run: world.last_change_tick(), + this_run: world.read_change_tick(), batching_strategy: BatchingStrategy::new(), } } @@ -832,9 +834,12 @@ impl QueryState { #[inline] pub fn par_iter_mut<'w, 's>(&'s mut self, world: &'w mut World) -> QueryParIter<'w, 's, Q, F> { self.update_archetypes(world); + let this_run = world.change_tick(); QueryParIter { world, state: self, + last_run: world.last_change_tick(), + this_run, batching_strategy: BatchingStrategy::new(), } } diff --git a/crates/bevy_ecs/src/reflect.rs b/crates/bevy_ecs/src/reflect.rs index bb4dbb1cf3a11..927973dea28bc 100644 --- a/crates/bevy_ecs/src/reflect.rs +++ b/crates/bevy_ecs/src/reflect.rs @@ -44,6 +44,8 @@ pub struct ReflectComponent(ReflectComponentFns); /// world. #[derive(Clone)] pub struct ReflectComponentFns { + /// Function pointer implementing [`ReflectComponent::from_world()`]. + pub from_world: fn(&mut World) -> Box, /// Function pointer implementing [`ReflectComponent::insert()`]. pub insert: fn(&mut EntityMut, &dyn Reflect), /// Function pointer implementing [`ReflectComponent::apply()`]. @@ -79,6 +81,11 @@ impl ReflectComponentFns { } impl ReflectComponent { + /// Constructs default reflected [`Component`] from world using [`from_world()`](FromWorld::from_world). + pub fn from_world(&self, world: &mut World) -> Box { + (self.0.from_world)(world) + } + /// Insert a reflected [`Component`] into the entity like [`insert()`](crate::world::EntityMut::insert). pub fn insert(&self, entity: &mut EntityMut, component: &dyn Reflect) { (self.0.insert)(entity, component); @@ -170,6 +177,7 @@ impl ReflectComponent { impl FromType for ReflectComponent { fn from_type() -> Self { ReflectComponent(ReflectComponentFns { + from_world: |world| Box::new(C::from_world(world)), insert: |entity, reflected_component| { let mut component = entity.world_scope(|world| C::from_world(world)); component.apply(reflected_component); diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index ba39981b63822..0ad7f0591744c 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -1,6 +1,11 @@ +use std::any::TypeId; use std::borrow::Cow; +use std::ops::Not; +use crate::component::{self, ComponentId}; +use crate::query::Access; use crate::system::{CombinatorSystem, Combine, IntoSystem, ReadOnlySystem, System}; +use crate::world::World; pub type BoxedCondition = Box>; @@ -26,7 +31,7 @@ pub trait Condition: sealed::Condition { /// # 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` in a system. /// my_system.run_if(resource_equals(R(0))), @@ -43,7 +48,7 @@ pub trait Condition: sealed::Condition { /// # 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::().and_then(resource_equals(R(0)))), /// ); @@ -81,7 +86,7 @@ pub trait Condition: sealed::Condition { /// # let mut world = World::new(); /// # #[derive(Resource)] struct C(bool); /// # fn my_system(mut c: ResMut) { 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::().or_else(resource_exists::())), /// ); @@ -131,18 +136,47 @@ mod sealed { } pub mod common_conditions { - use super::Condition; + use std::borrow::Cow; + + use super::NotSystem; use crate::{ change_detection::DetectChanges, event::{Event, EventReader}, prelude::{Component, Query, With}, schedule::{State, States}, - system::{In, IntoPipeSystem, Res, Resource}, + system::{IntoSystem, Res, Resource, System}, }; /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if the first time the condition is run and false every time after - pub fn run_once() -> impl FnMut() -> bool { + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// app.add_systems( + /// // `run_once` will only return true the first time it's evaluated + /// my_system.run_if(run_once()), + /// ); + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // This is the first time the condition will be evaluated so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// // This is the seconds time the condition will be evaluated so `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// ``` + pub fn run_once() -> impl FnMut() -> bool + Clone { let mut has_run = false; move || { if !has_run { @@ -156,7 +190,33 @@ pub mod common_conditions { /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if the resource exists. - pub fn resource_exists() -> impl FnMut(Option>) -> bool + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// app.add_systems( + /// // `resource_exsists` will only return true if the given resource exsists in the world + /// my_system.run_if(resource_exists::()), + /// ); + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // `Counter` hasn't been added so `my_system` won't run + /// app.run(&mut world); + /// world.init_resource::(); + /// + /// // `Counter` has now been added so `my_system` can run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// ``` + pub fn resource_exists() -> impl FnMut(Option>) -> bool + Clone where T: Resource, { @@ -169,6 +229,33 @@ pub mod common_conditions { /// # Panics /// /// The condition will panic if the resource does not exist. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default, PartialEq)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// 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))), + /// ); + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // `Counter` is `0` so `my_system` can run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// // `Counter` is no longer `0` so `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// ``` pub fn resource_equals(value: T) -> impl FnMut(Res) -> bool where T: Resource + PartialEq, @@ -180,6 +267,37 @@ pub mod common_conditions { /// if the resource exists and is equal to `value`. /// /// The condition will return `false` if the resource does not exist. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default, PartialEq)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// 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))), + /// ); + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // `Counter` hasn't been added so `my_system` can't run + /// app.run(&mut world); + /// world.init_resource::(); + /// + /// // `Counter` is `0` so `my_system` can run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// // `Counter` is no longer `0` so `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// ``` pub fn resource_exists_and_equals(value: T) -> impl FnMut(Option>) -> bool where T: Resource + PartialEq, @@ -192,7 +310,36 @@ pub mod common_conditions { /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if the resource of the given type has been added since the condition was last checked. - pub fn resource_added() -> impl FnMut(Option>) -> bool + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// app.add_systems( + /// // `resource_added` will only return true if the + /// // given resource was just added + /// my_system.run_if(resource_added::()), + /// ); + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// world.init_resource::(); + /// + /// // `Counter` was just added so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// // `Counter` was not just added so `my_system` will not run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// ``` + pub fn resource_added() -> impl FnMut(Option>) -> bool + Clone where T: Resource, { @@ -213,7 +360,43 @@ pub mod common_conditions { /// # Panics /// /// The condition will panic if the resource does not exist. - pub fn resource_changed() -> impl FnMut(Res) -> bool + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// app.add_systems( + /// // `resource_changed` will only return true if the + /// // given resource was just changed (or added) + /// my_system.run_if( + /// resource_changed::() + /// // By default detecting changes will also trigger if the resource was + /// // just added, this won't work with my example so I will addd a second + /// // condition to make sure the resource wasn't just added + /// .and_then(not(resource_added::())) + /// ), + /// ); + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // `Counter` hasn't been changed so `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// + /// world.resource_mut::().0 = 50; + /// + /// // `Counter` was just changed so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 51); + /// ``` + pub fn resource_changed() -> impl FnMut(Res) -> bool + Clone where T: Resource, { @@ -231,7 +414,46 @@ pub mod common_conditions { /// This run condition does not detect when the resource is removed. /// /// The condition will return `false` if the resource does not exist. - pub fn resource_exists_and_changed() -> impl FnMut(Option>) -> bool + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// 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( + /// resource_exists_and_changed::() + /// // By default detecting changes will also trigger if the resource was + /// // just added, this won't work with my example so I will addd a second + /// // condition to make sure the resource wasn't just added + /// .and_then(not(resource_added::())) + /// ), + /// ); + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // `Counter` doesn't exist so `my_system` won't run + /// app.run(&mut world); + /// world.init_resource::(); + /// + /// // `Counter` hasn't been changed so `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// + /// world.resource_mut::().0 = 50; + /// + /// // `Counter` was just changed so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 51); + /// ``` + pub fn resource_exists_and_changed() -> impl FnMut(Option>) -> bool + Clone where T: Resource, { @@ -253,7 +475,57 @@ pub mod common_conditions { /// has been removed since the run condition was last checked. /// /// The condition will return `false` if the resource does not exist. - pub fn resource_changed_or_removed() -> impl FnMut(Option>) -> bool + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// 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( + /// resource_changed_or_removed::() + /// // By default detecting changes will also trigger if the resource was + /// // just added, this won't work with my example so I will addd a second + /// // condition to make sure the resource wasn't just added + /// .and_then(not(resource_added::())) + /// ), + /// ); + /// + /// #[derive(Resource, Default)] + /// struct MyResource; + /// + /// // If `Counter` exists, increment it, otherwise insert `MyResource` + /// fn my_system(mut commands: Commands, mut counter: Option>) { + /// if let Some(mut counter) = counter { + /// counter.0 += 1; + /// } else { + /// commands.init_resource::(); + /// } + /// } + /// + /// // `Counter` hasn't been changed so `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// + /// world.resource_mut::().0 = 50; + /// + /// // `Counter` was just changed so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 51); + /// + /// world.remove_resource::(); + /// + /// // `Counter` was just removed so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.contains_resource::(), true); + /// ``` + pub fn resource_changed_or_removed() -> impl FnMut(Option>) -> bool + Clone where T: Resource, { @@ -273,7 +545,42 @@ pub mod common_conditions { /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if the resource of the given type has been removed since the condition was last checked. - pub fn resource_removed() -> impl FnMut(Option>) -> bool + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// app.add_systems( + /// // `resource_removed` will only return true if the + /// // given resource was just removed + /// my_system.run_if(resource_removed::()), + /// ); + /// + /// #[derive(Resource, Default)] + /// struct MyResource; + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// world.init_resource::(); + /// + /// // `MyResource` hasn't just been removed so `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// + /// world.remove_resource::(); + /// + /// // `MyResource` was just removed so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// ``` + pub fn resource_removed() -> impl FnMut(Option>) -> bool + Clone where T: Resource, { @@ -293,7 +600,44 @@ pub mod common_conditions { /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if the state machine exists. - pub fn state_exists() -> impl FnMut(Option>>) -> bool { + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// #[derive(States, Clone, Copy, Default, Eq, PartialEq, Hash, Debug)] + /// enum GameState { + /// #[default] + /// Playing, + /// Paused, + /// } + /// + /// app.add_systems( + /// // `state_exists` will only return true if the + /// // given state exsists + /// my_system.run_if(state_exists::()), + /// ); + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // `GameState` does not yet exist `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// + /// world.init_resource::>(); + /// + /// // `GameState` now exists so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// ``` + pub fn state_exists() -> impl FnMut(Option>>) -> bool + Clone { move |current_state: Option>>| current_state.is_some() } @@ -303,7 +647,51 @@ pub mod common_conditions { /// # Panics /// /// The condition will panic if the resource does not exist. - pub fn in_state(state: S) -> impl FnMut(Res>) -> bool { + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// #[derive(States, Clone, Copy, Default, Eq, PartialEq, Hash, Debug)] + /// enum GameState { + /// #[default] + /// Playing, + /// Paused, + /// } + /// + /// world.init_resource::>(); + /// + /// app.add_systems(( + /// // `in_state` will only return true if the + /// // given state equals the given value + /// play_system.run_if(in_state(GameState::Playing)), + /// pause_system.run_if(in_state(GameState::Paused)), + /// )); + /// + /// fn play_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// fn pause_system(mut counter: ResMut) { + /// counter.0 -= 1; + /// } + /// + /// // We default to `GameState::Playing` so `play_system` runs + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// *world.resource_mut::>() = State(GameState::Paused); + /// + /// // Now that we are in `GameState::Pause`, `pause_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// ``` + pub fn in_state(state: S) -> impl FnMut(Res>) -> bool + Clone { move |current_state: Res>| current_state.0 == state } @@ -311,9 +699,57 @@ pub mod common_conditions { /// if the state machine exists and is currently in `state`. /// /// The condition will return `false` if the state does not exist. + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// #[derive(States, Clone, Copy, Default, Eq, PartialEq, Hash, Debug)] + /// enum GameState { + /// #[default] + /// Playing, + /// Paused, + /// } + /// + /// app.add_systems(( + /// // `state_exists_and_equals` will only return true if the + /// // given state exsists and equals the given value + /// play_system.run_if(state_exists_and_equals(GameState::Playing)), + /// pause_system.run_if(state_exists_and_equals(GameState::Paused)), + /// )); + /// + /// fn play_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// fn pause_system(mut counter: ResMut) { + /// counter.0 -= 1; + /// } + /// + /// // `GameState` does not yet exists so neither system will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// + /// world.init_resource::>(); + /// + /// // We default to `GameState::Playing` so `play_system` runs + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// *world.resource_mut::>() = State(GameState::Paused); + /// + /// // Now that we are in `GameState::Pause`, `pause_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// ``` pub fn state_exists_and_equals( state: S, - ) -> impl FnMut(Option>>) -> bool { + ) -> impl FnMut(Option>>) -> bool + Clone { move |current_state: Option>>| match current_state { Some(current_state) => current_state.0 == state, None => false, @@ -329,13 +765,90 @@ pub mod common_conditions { /// # Panics /// /// The condition will panic if the resource does not exist. - pub fn state_changed() -> impl FnMut(Res>) -> bool { + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// #[derive(States, Clone, Copy, Default, Eq, PartialEq, Hash, Debug)] + /// enum GameState { + /// #[default] + /// Playing, + /// Paused, + /// } + /// + /// world.init_resource::>(); + /// + /// 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 + /// my_system.run_if(state_changed::()), + /// ); + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // `GameState` has just been added so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// // `GameState` has not been updated so `my_system` will not run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// + /// *world.resource_mut::>() = State(GameState::Paused); + /// + /// // Now that `GameState` has been updated `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 2); + /// ``` + pub fn state_changed() -> impl FnMut(Res>) -> bool + Clone { move |current_state: Res>| current_state.is_changed() } /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if there are any new events of the given type since it was last called. - pub fn on_event() -> impl FnMut(EventReader) -> bool { + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// # world.init_resource::>(); + /// # app.add_systems(Events::::update_system.before(my_system)); + /// + /// app.add_systems( + /// my_system.run_if(on_event::()), + /// ); + /// + /// struct MyEvent; + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // No new `MyEvent` events have been push so `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// + /// world.resource_mut::>().send(MyEvent); + /// + /// // A `MyEvent` event has been push so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// ``` + pub fn on_event() -> impl FnMut(EventReader) -> bool + Clone { // The events need to be consumed, so that there are no false positives on subsequent // calls of the run condition. Simply checking `is_empty` would not be enough. // PERF: note that `count` is efficient (not actually looping/iterating), @@ -345,39 +858,169 @@ pub mod common_conditions { /// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true` /// if there are any entities with the given component type. - pub fn any_with_component() -> impl FnMut(Query<(), With>) -> bool { + /// + /// # Example + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); + /// # let mut world = World::new(); + /// # world.init_resource::(); + /// app.add_systems( + /// my_system.run_if(any_with_component::()), + /// ); + /// + /// #[derive(Component)] + /// struct MyComponent; + /// + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; + /// } + /// + /// // No entities exist yet with a `MyComponent` component so `my_system` won't run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); + /// + /// world.spawn(MyComponent); + /// + /// // An entities with `MyComponent` now exists so `my_system` will run + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 1); + /// ``` + pub fn any_with_component() -> impl FnMut(Query<(), With>) -> bool + Clone { move |query: Query<(), With>| !query.is_empty() } /// Generates a [`Condition`](super::Condition) that inverses the result of passed one. /// - /// # Examples + /// # Example /// /// ``` - /// use bevy_ecs::prelude::*; - /// // Building a new schedule/app... - /// let mut sched = Schedule::default(); - /// sched.add_system( - /// // This system will never run. - /// my_system.run_if(not(always_true)) - /// ) - /// // ... - /// # ; + /// # use bevy_ecs::prelude::*; + /// # #[derive(Resource, Default)] + /// # struct Counter(u8); + /// # let mut app = Schedule::new(); /// # let mut world = World::new(); - /// # sched.run(&mut world); + /// # world.init_resource::(); + /// app.add_systems( + /// // `not` will inverse any condition you pass in. + /// // Since the condition we choose always returns true + /// // this system will never run + /// my_system.run_if(not(always)), + /// ); /// - /// // A condition that always returns true. - /// fn always_true() -> bool { - /// true + /// fn my_system(mut counter: ResMut) { + /// counter.0 += 1; /// } - /// # - /// # fn my_system() { unreachable!() } + /// + /// fn always() -> bool { + /// true + /// } + /// + /// app.run(&mut world); + /// assert_eq!(world.resource::().0, 0); /// ``` - pub fn not(condition: impl Condition) -> impl Condition<()> { - condition.pipe(|In(val): In| !val) + pub fn not(condition: T) -> NotSystem + where + TOut: std::ops::Not, + T: IntoSystem<(), TOut, Marker>, + { + let condition = IntoSystem::into_system(condition); + let name = format!("!{}", condition.name()); + NotSystem:: { + condition, + name: Cow::Owned(name), + } } } +/// Invokes [`Not`] with the output of another system. +/// +/// See [`common_conditions::not`] for examples. +#[derive(Clone)] +pub struct NotSystem +where + T::Out: Not, +{ + condition: T, + name: Cow<'static, str>, +} +impl System for NotSystem +where + T::Out: Not, +{ + type In = T::In; + type Out = ::Output; + + fn name(&self) -> Cow<'static, str> { + self.name.clone() + } + + fn type_id(&self) -> TypeId { + TypeId::of::() + } + + fn component_access(&self) -> &Access { + self.condition.component_access() + } + + fn archetype_component_access(&self) -> &Access { + self.condition.archetype_component_access() + } + + fn is_send(&self) -> bool { + self.condition.is_send() + } + + fn is_exclusive(&self) -> bool { + self.condition.is_exclusive() + } + + unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out { + // SAFETY: The inner condition system asserts its own safety. + !self.condition.run_unsafe(input, world) + } + + fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out { + !self.condition.run(input, world) + } + + fn apply_buffers(&mut self, world: &mut World) { + self.condition.apply_buffers(world); + } + + fn initialize(&mut self, world: &mut World) { + self.condition.initialize(world); + } + + fn update_archetype_component_access(&mut self, world: &World) { + self.condition.update_archetype_component_access(world); + } + + fn check_change_tick(&mut self, change_tick: component::Tick) { + self.condition.check_change_tick(change_tick); + } + + fn get_last_run(&self) -> component::Tick { + self.condition.get_last_run() + } + + fn set_last_run(&mut self, last_run: component::Tick) { + self.condition.set_last_run(last_run); + } +} + +// SAFETY: This trait is only implemented when the inner system is read-only. +// The `Not` condition does not modify access, and thus cannot transform a read-only system into one that is not. +unsafe impl ReadOnlySystem for NotSystem +where + T: ReadOnlySystem, + T::Out: Not, +{ +} + /// Combines the outputs of two systems using the `&&` operator. pub type AndThen = CombinatorSystem; @@ -425,3 +1068,142 @@ where a(input) || b(input) } } + +#[cfg(test)] +mod tests { + use super::{common_conditions::*, Condition}; + use crate as bevy_ecs; + 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)] + struct Counter(usize); + + fn increment_counter(mut counter: ResMut) { + counter.0 += 1; + } + + fn every_other_time(mut has_ran: Local) -> bool { + *has_ran = !*has_ran; + *has_ran + } + + #[test] + fn run_condition() { + let mut world = World::new(); + world.init_resource::(); + let mut schedule = Schedule::new(); + + // Run every other cycle + schedule.add_systems(increment_counter.run_if(every_other_time)); + + schedule.run(&mut world); + schedule.run(&mut world); + assert_eq!(world.resource::().0, 1); + schedule.run(&mut world); + schedule.run(&mut world); + assert_eq!(world.resource::().0, 2); + + // Run every other cycle oppsite to the last one + schedule.add_systems(increment_counter.run_if(not(every_other_time))); + + schedule.run(&mut world); + schedule.run(&mut world); + assert_eq!(world.resource::().0, 4); + schedule.run(&mut world); + schedule.run(&mut world); + assert_eq!(world.resource::().0, 6); + } + + #[test] + fn run_condition_combinators() { + let mut world = World::new(); + world.init_resource::(); + let mut schedule = Schedule::new(); + + // Always run + schedule.add_systems(increment_counter.run_if(every_other_time.or_else(|| true))); + // Run every other cycle + schedule.add_systems(increment_counter.run_if(every_other_time.and_then(|| true))); + + schedule.run(&mut world); + assert_eq!(world.resource::().0, 2); + schedule.run(&mut world); + assert_eq!(world.resource::().0, 3); + } + + #[test] + fn multiple_run_conditions() { + let mut world = World::new(); + world.init_resource::(); + let mut schedule = Schedule::new(); + + // Run every other cycle + schedule.add_systems(increment_counter.run_if(every_other_time).run_if(|| true)); + // Never run + schedule.add_systems(increment_counter.run_if(every_other_time).run_if(|| false)); + + schedule.run(&mut world); + assert_eq!(world.resource::().0, 1); + schedule.run(&mut world); + assert_eq!(world.resource::().0, 1); + } + + #[test] + fn multiple_run_conditions_is_and_operation() { + let mut world = World::new(); + world.init_resource::(); + + let mut schedule = Schedule::new(); + + // This should never run, if multiple run conditions worked + // like an OR condition then it would always run + schedule.add_systems( + increment_counter + .run_if(every_other_time) + .run_if(not(every_other_time)), + ); + + schedule.run(&mut world); + assert_eq!(world.resource::().0, 0); + schedule.run(&mut world); + assert_eq!(world.resource::().0, 0); + } + + #[derive(States, PartialEq, Eq, Debug, Default, Hash, Clone)] + enum TestState { + #[default] + A, + B, + } + + #[derive(Component)] + struct TestComponent; + + fn test_system() {} + + // Ensure distributive_run_if compiles with the common conditions. + #[test] + fn distributive_run_if_compiles() { + Schedule::default().add_systems( + (test_system, test_system) + .distributive_run_if(run_once()) + .distributive_run_if(resource_exists::>()) + .distributive_run_if(resource_added::>()) + .distributive_run_if(resource_changed::>()) + .distributive_run_if(resource_exists_and_changed::>()) + .distributive_run_if(resource_changed_or_removed::>()) + .distributive_run_if(resource_removed::>()) + .distributive_run_if(state_exists::()) + .distributive_run_if(in_state(TestState::A)) + .distributive_run_if(state_changed::()) + .distributive_run_if(on_event::()) + .distributive_run_if(any_with_component::()) + .distributive_run_if(not(run_once())), + ); + } +} diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index 54eb9f863f5b7..50aa7bfe9010a 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -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, -} - -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, -} - -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(condition: impl Condition) -> BoxedCondition { let condition_system = IntoSystem::into_system(condition); assert!( @@ -79,288 +33,147 @@ fn ambiguous_with(graph_info: &mut GraphInfo, set: BoxedSystemSet) { } } -/// Types that can be converted into a [`SystemSetConfig`]. -/// -/// This has been implemented for all types that implement [`SystemSet`] and boxed trait objects. -pub trait IntoSystemSetConfig: Sized { - /// Convert into a [`SystemSetConfig`]. - #[doc(hidden)] - fn into_config(self) -> SystemSetConfig; - /// Add to the provided `set`. - #[track_caller] - fn in_set(self, set: impl FreeSystemSet) -> 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(self, set: impl IntoSystemSet) -> SystemSetConfig { - self.into_config().before(set) - } - /// Run after all systems in `set`. - fn after(self, set: impl IntoSystemSet) -> SystemSetConfig { - self.into_config().after(set) - } - /// Run the systems in this set only if the [`Condition`] is `true`. - /// - /// The `Condition` will be evaluated at most once (per schedule run), - /// the first time a system in this set prepares to run. - fn run_if(self, condition: impl Condition) -> SystemSetConfig { - self.into_config().run_if(condition) - } - /// Suppress warnings and errors that would result from systems in this set having ambiguities - /// (conflicting access but indeterminate order) with systems in `set`. - fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemSetConfig { - self.into_config().ambiguous_with(set) - } - /// Suppress warnings and errors that would result from systems in this set having ambiguities - /// (conflicting access but indeterminate order) with any other system. - fn ambiguous_with_all(self) -> SystemSetConfig { - self.into_config().ambiguous_with_all() - } -} - -impl IntoSystemSetConfig for S { - fn into_config(self) -> SystemSetConfig { - SystemSetConfig::new(Box::new(self)) - } -} - -impl IntoSystemSetConfig for BoxedSystemSet { - fn into_config(self) -> SystemSetConfig { - SystemSetConfig::new(self) - } -} - -impl IntoSystemSetConfig for SystemSetConfig { - 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(), - "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(mut self, set: impl IntoSystemSet) -> Self { - self.graph_info.dependencies.push(Dependency::new( - DependencyKind::Before, - Box::new(set.into_system_set()), - )); - self - } - - fn after(mut self, set: impl IntoSystemSet) -> Self { - self.graph_info.dependencies.push(Dependency::new( - DependencyKind::After, - Box::new(set.into_system_set()), - )); - self - } - - fn run_if(mut self, condition: impl Condition) -> Self { - self.conditions.push(new_condition(condition)); - self - } - - fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { - ambiguous_with(&mut self.graph_info, Box::new(set.into_system_set())); - self - } - - fn ambiguous_with_all(mut self) -> Self { - self.graph_info.ambiguous_with = Ambiguity::IgnoreAll; - self - } -} - -/// Types that can be converted into a [`SystemConfig`]. -/// -/// This has been implemented for boxed [`System`](crate::system::System) -/// trait objects and all functions that turn into such. -pub trait IntoSystemConfig: Sized +impl IntoSystemConfigs for F where - Config: IntoSystemConfig<(), Config>, + F: IntoSystem<(), (), Marker>, { - /// 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(self, set: impl IntoSystemSet) -> Config { - self.into_config().before(set) - } - /// Run after all systems in `set`. - fn after(self, set: impl IntoSystemSet) -> 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(self, condition: impl Condition) -> 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(self, set: impl IntoSystemSet) -> 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() + fn into_configs(self) -> SystemConfigs { + SystemConfigs::new_system(Box::new(IntoSystem::into_system(self))) } } -impl IntoSystemConfig for F -where - F: IntoSystem<(), (), Marker>, -{ - fn into_config(self) -> SystemConfig { - SystemConfig::new(Box::new(IntoSystem::into_system(self))) +impl IntoSystemConfigs<()> for BoxedSystem<(), ()> { + fn into_configs(self) -> SystemConfigs { + SystemConfigs::new_system(self) } } -impl IntoSystemConfig<()> for BoxedSystem<(), ()> { - fn into_config(self) -> SystemConfig { - SystemConfig::new(self) - } +pub struct SystemConfig { + pub(crate) system: BoxedSystem, + pub(crate) graph_info: GraphInfo, + pub(crate) conditions: Vec, } -impl IntoSystemConfig<()> for SystemConfig { - fn into_config(self) -> Self { - self - } +/// A collection of [`SystemConfig`]. +pub enum SystemConfigs { + SystemConfig(SystemConfig), + Configs { + configs: Vec, + /// If `true`, adds `before -> after` ordering constraints between the successive elements. + chained: bool, + }, +} - #[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 +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(), + }) } - #[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 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 no_default_base_set(mut self) -> SystemConfig { - self.graph_info.add_default_base_set = false; - self + 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 before(mut self, set: impl IntoSystemSet) -> Self { - self.graph_info.dependencies.push(Dependency::new( - DependencyKind::Before, - Box::new(set.into_system_set()), - )); - self + fn after_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 after(mut self, set: impl IntoSystemSet) -> Self { - self.graph_info.dependencies.push(Dependency::new( - DependencyKind::After, - Box::new(set.into_system_set()), - )); - self + fn distributive_run_if_inner(&mut self, condition: impl Condition + 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 run_if(mut self, condition: impl Condition) -> Self { - self.conditions.push(new_condition(condition)); - self + 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(mut self, set: impl IntoSystemSet) -> Self { - ambiguous_with(&mut self.graph_info, Box::new(set.into_system_set())); - self + 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 ambiguous_with_all(mut self) -> Self { - self.graph_info.ambiguous_with = Ambiguity::IgnoreAll; - self + 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") + } + } } } -/// A collection of [`SystemConfig`]. -pub struct SystemConfigs { - pub(super) systems: Vec, - /// If `true`, adds `before -> after` ordering constraints between the successive elements. - pub(super) chained: bool, -} - /// Types that can convert into a [`SystemConfigs`]. pub trait IntoSystemConfigs where @@ -372,16 +185,10 @@ where /// Add these systems to the provided `set`. #[track_caller] - fn in_set(self, set: impl FreeSystemSet) -> SystemConfigs { + fn in_set(self, set: impl SystemSet) -> 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(self, set: impl IntoSystemSet) -> SystemConfigs { self.into_configs().before(set) @@ -400,7 +207,7 @@ where /// 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 + /// This is equivalent to calling [`run_if`](IntoSystemConfigs::run_if) on each individual /// system, as shown below: /// /// ``` @@ -426,6 +233,14 @@ where 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(self, condition: impl Condition) -> 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(self, set: impl IntoSystemSet) -> SystemConfigs { @@ -444,6 +259,35 @@ where 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 { @@ -457,85 +301,225 @@ impl IntoSystemConfigs<()> for SystemConfigs { 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.in_set_inner(set.dyn_clone()); self } fn before(mut self, set: impl IntoSystemSet) -> Self { let set = set.into_system_set(); - for config in &mut self.systems { - config - .graph_info - .dependencies - .push(Dependency::new(DependencyKind::Before, set.dyn_clone())); - } - + self.before_inner(set.dyn_clone()); self } fn after(mut self, set: impl IntoSystemSet) -> Self { let set = set.into_system_set(); - for config in &mut self.systems { - config - .graph_info - .dependencies - .push(Dependency::new(DependencyKind::After, set.dyn_clone())); - } - + self.after_inner(set.dyn_clone()); self } fn distributive_run_if(mut self, condition: impl Condition + Clone) -> SystemConfigs { - for config in &mut self.systems { - config.conditions.push(new_condition(condition.clone())); - } - + self.distributive_run_if_inner(condition); self } fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { let set = set.into_system_set(); - for config in &mut self.systems { - ambiguous_with(&mut config.graph_info, set.dyn_clone()); - } - + self.ambiguous_with_inner(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.ambiguous_with_all_inner(); + self + } + fn run_if(mut self, condition: impl Condition) -> SystemConfigs { + self.run_if_inner(new_condition(condition)); self } fn chain(mut self) -> Self { - self.chained = true; + 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, +} + +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. +pub trait IntoSystemSetConfig: Sized { + /// Convert into a [`SystemSetConfig`]. + #[doc(hidden)] + fn into_config(self) -> SystemSetConfig; + /// Add to the provided `set`. + #[track_caller] + fn in_set(self, set: impl SystemSet) -> SystemSetConfig { + self.into_config().in_set(set) + } + /// Run before all systems in `set`. + fn before(self, set: impl IntoSystemSet) -> SystemSetConfig { + self.into_config().before(set) + } + /// Run after all systems in `set`. + fn after(self, set: impl IntoSystemSet) -> SystemSetConfig { + self.into_config().after(set) + } + /// Run the systems in this set only if the [`Condition`] is `true`. + /// + /// The `Condition` will be evaluated at most once (per schedule run), + /// the first time a system in this set prepares to run. + fn run_if(self, condition: impl Condition) -> SystemSetConfig { + self.into_config().run_if(condition) + } + /// Suppress warnings and errors that would result from systems in this set having ambiguities + /// (conflicting access but indeterminate order) with systems in `set`. + fn ambiguous_with(self, set: impl IntoSystemSet) -> SystemSetConfig { + self.into_config().ambiguous_with(set) + } + /// Suppress warnings and errors that would result from systems in this set having ambiguities + /// (conflicting access but indeterminate order) with any other system. + fn ambiguous_with_all(self) -> SystemSetConfig { + 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 IntoSystemSetConfig for S { + fn into_config(self) -> SystemSetConfig { + SystemSetConfig::new(Box::new(self)) + } +} + +impl IntoSystemSetConfig for BoxedSystemSet { + fn into_config(self) -> SystemSetConfig { + SystemSetConfig::new(self) + } +} + +impl IntoSystemSetConfig for SystemSetConfig { + 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" + ); + self.graph_info.sets.push(Box::new(set)); + self + } + + fn before(mut self, set: impl IntoSystemSet) -> Self { + self.graph_info.dependencies.push(Dependency::new( + DependencyKind::Before, + Box::new(set.into_system_set()), + )); + self + } + + fn after(mut self, set: impl IntoSystemSet) -> Self { + self.graph_info.dependencies.push(Dependency::new( + DependencyKind::After, + Box::new(set.into_system_set()), + )); + self + } + + fn run_if(mut self, condition: impl Condition) -> Self { + self.conditions.push(new_condition(condition)); + self + } + + fn ambiguous_with(mut self, set: impl IntoSystemSet) -> Self { + ambiguous_with(&mut self.graph_info, Box::new(set.into_system_set())); + self + } + + fn ambiguous_with_all(mut self) -> Self { + self.graph_info.ambiguous_with = Ambiguity::IgnoreAll; self } } @@ -558,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(self, set: impl IntoSystemSet) -> SystemSetConfigs { self.into_configs().before(set) @@ -596,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 { @@ -609,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(mut self, set: impl IntoSystemSet) -> Self { let set = set.into_system_set(); for config in &mut self.sets { @@ -692,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,)*) @@ -726,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); diff --git a/crates/bevy_ecs/src/schedule/graph_utils.rs b/crates/bevy_ecs/src/schedule/graph_utils.rs index f7e28e6bb5cd9..4a0311c8beedd 100644 --- a/crates/bevy_ecs/src/schedule/graph_utils.rs +++ b/crates/bevy_ecs/src/schedule/graph_utils.rs @@ -67,56 +67,14 @@ pub(crate) enum Ambiguity { IgnoreAll, } -#[derive(Clone)] +#[derive(Clone, Default)] pub(crate) struct GraphInfo { pub(crate) sets: Vec, pub(crate) dependencies: Vec, pub(crate) ambiguous_with: Ambiguity, - pub(crate) add_default_base_set: bool, pub(crate) base_set: Option, } -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); diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index 0a5f92a4ddb35..05aaba9f8b33c 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -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::(); - schedule.add_system(make_function_system(0)); + schedule.add_systems(make_function_system(0)); schedule.run(&mut world); assert_eq!(world.resource::().0, vec![0]); @@ -88,7 +88,7 @@ mod tests { world.init_resource::(); - schedule.add_system(make_exclusive_system(0)); + schedule.add_systems(make_exclusive_system(0)); schedule.run(&mut world); assert_eq!(world.resource::().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::().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::(); + + 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::().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::(); world.init_resource::(); - schedule.add_system( + schedule.add_systems( make_function_system(0).run_if(|condition: Res| condition.0), ); @@ -256,7 +300,7 @@ mod tests { world.init_resource::(); world.init_resource::(); - schedule.add_system( + schedule.add_systems( make_exclusive_system(0).run_if(|condition: Res| condition.0), ); @@ -294,13 +338,13 @@ mod tests { world.init_resource::(); 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::().0.load(Ordering::Relaxed), 1); @@ -314,13 +358,13 @@ mod tests { world.init_resource::(); 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::().0.load(Ordering::Relaxed), 1); @@ -337,7 +381,7 @@ mod tests { world.init_resource::(); let mut schedule = Schedule::default(); - schedule.add_system( + schedule.add_systems( counting_system .run_if(|res1: Res| res1.is_changed()) .run_if(|res2: Res| res2.is_changed()), @@ -391,7 +435,7 @@ mod tests { .run_if(|res2: Res| 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| res1.is_changed())); - schedule.add_system( + schedule.add_systems( counting_system .run_if(|res2: Res| 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::(); - - 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::().0, vec![1, 0]); - } - } } diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 2f7521351076e..303ac8dd29479 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -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 { 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 { - 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, 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 { /// 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(&mut self, system: impl IntoSystemConfig) -> &mut Self { - self.graph.add_system(system); + #[deprecated(since = "0.11.0", note = "please use `add_systems` instead")] + pub fn add_system(&mut self, system: impl IntoSystemConfigs) -> &mut Self { + self.graph.add_systems_inner(system.into_configs(), false); self } /// Add a collection of systems to the schedule. pub fn add_systems(&mut self, systems: impl IntoSystemConfigs) -> &mut Self { - self.graph.add_systems(systems); + self.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, - 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>>, system_set_ids: HashMap, uninit: Vec<(NodeId, usize)>, - maybe_default_base_set: Vec, hierarchy: Dag, dependency: Dag, dependency_flattened: Dag, @@ -426,7 +390,6 @@ pub struct ScheduleGraph { conflicting_systems: Vec<(NodeId, NodeId, Vec)>, changed: bool, settings: ScheduleBuildSettings, - default_base_set: Option, } 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, - BaseSetMembership, - &[BoxedCondition], - ), - > { + ) -> impl Iterator, &[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 { + pub fn system_sets(&self) -> impl Iterator { 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(&mut self, systems: impl IntoSystemConfigs) { - let SystemConfigs { systems, chained } = systems.into_configs(); - let mut system_iter = systems.into_iter(); - if chained { - let Some(prev) = system_iter.next() else { return }; - let mut prev_id = self.add_system_inner(prev).unwrap(); - for next in system_iter { - let next_id = self.add_system_inner(next).unwrap(); - self.dependency.graph.add_edge(prev_id, next_id, ()); - prev_id = next_id; + /// 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 ¤t_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 ¤t_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(&mut self, system: impl IntoSystemConfig) { - self.add_system_inner(system).unwrap(); - } - - fn add_system_inner( - &mut self, - system: impl IntoSystemConfig, - ) -> Result { - let SystemConfig { - system, - graph_info, - conditions, - } = system.into_config(); - + fn add_system_inner(&mut self, config: SystemConfig) -> Result { 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, 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 = 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 { - 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) { - 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, + /// True if and only if all nodes are "densely chained" + densely_chained: bool, } /// Used to select the appropriate reporting function. diff --git a/crates/bevy_ecs/src/schedule/set.rs b/crates/bevy_ecs/src/schedule/set.rs index 23058a3de9fbc..56deb5c398ae8 100644 --- a/crates/bevy_ecs/src/schedule/set.rs +++ b/crates/bevy_ecs/src/schedule/set.rs @@ -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; } -/// 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()) diff --git a/crates/bevy_ecs/src/schedule/state.rs b/crates/bevy_ecs/src/schedule/state.rs index abb001e90a8d0..326c99d2afecd 100644 --- a/crates/bevy_ecs/src/schedule/state.rs +++ b/crates/bevy_ecs/src/schedule/state.rs @@ -4,7 +4,7 @@ use std::mem; use crate as bevy_ecs; use crate::change_detection::DetectChangesMut; -use crate::schedule::{ScheduleLabel, Schedules, SystemSet}; +use crate::schedule::{ScheduleLabel, SystemSet}; use crate::system::Resource; use crate::world::World; @@ -100,15 +100,18 @@ impl NextState { } } -/// Run the enter schedule for the current state +/// Run the enter schedule (if it exists) for the current state. pub fn run_enter_schedule(world: &mut World) { - world.run_schedule(OnEnter(world.resource::>().0.clone())); + world + .try_run_schedule(OnEnter(world.resource::>().0.clone())) + .ok(); } /// If a new state is queued in [`NextState`], this system: /// - Takes the new state value from [`NextState`] and updates [`State`]. -/// - Runs the [`OnExit(exited_state)`] schedule. -/// - Runs the [`OnEnter(entered_state)`] schedule. +/// - Runs the [`OnExit(exited_state)`] schedule, if it exists. +/// - Runs the [`OnTransition { from: exited_state, to: entered_state }`](OnTransition), if it exists. +/// - Runs the [`OnEnter(entered_state)`] schedule, if it exists. pub fn apply_state_transition(world: &mut World) { // We want to take the `NextState` resource, // but only mark it as changed if it wasn't empty. @@ -117,16 +120,15 @@ pub fn apply_state_transition(world: &mut World) { next_state_resource.set_changed(); let exited = mem::replace(&mut world.resource_mut::>().0, entered.clone()); - world.run_schedule(OnExit(exited.clone())); - let transition_schedule = OnTransition { - from: exited, - to: entered.clone(), - }; - if world.resource::().contains(&transition_schedule) { - world.run_schedule(transition_schedule); - } - - world.run_schedule(OnEnter(entered)); + // Try to run the schedules if they exist. + world.try_run_schedule(OnExit(exited.clone())).ok(); + world + .try_run_schedule(OnTransition { + from: exited, + to: entered.clone(), + }) + .ok(); + world.try_run_schedule(OnEnter(entered)).ok(); } } diff --git a/crates/bevy_ecs/src/system/combinator.rs b/crates/bevy_ecs/src/system/combinator.rs index ec782e715bd19..671a29431c883 100644 --- a/crates/bevy_ecs/src/system/combinator.rs +++ b/crates/bevy_ecs/src/system/combinator.rs @@ -50,7 +50,7 @@ use super::{ReadOnlySystem, System}; /// # world.init_resource::(); /// # /// # 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. diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 73df904177cd1..fb0f1b388fc18 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -582,9 +582,9 @@ impl<'w, 's> Commands<'w, 's> { /// # world.init_resource::(); /// # /// # 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); diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 0e88ad0187af4..d8bf01392ff4a 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -367,6 +367,9 @@ pub struct In(pub In); /// becomes the functions [`In`] tagged parameter or `()` if no such parameter exists. /// /// [`FunctionSystem`] must be `.initialized` before they can be run. +/// +/// The [`Clone`] implementation for [`FunctionSystem`] returns a new instance which +/// is NOT initialized. The cloned system must also be `.initialized` before it can be run. pub struct FunctionSystem where F: SystemParamFunction, @@ -380,6 +383,23 @@ where marker: PhantomData Marker>, } +// De-initializes the cloned system. +impl Clone for FunctionSystem +where + F: SystemParamFunction + Clone, +{ + fn clone(&self) -> Self { + Self { + func: self.func.clone(), + param_state: None, + system_meta: SystemMeta::new::(), + world_id: None, + archetype_generation: ArchetypeGeneration::initial(), + marker: PhantomData, + } + } +} + pub struct IsFunctionSystem; impl IntoSystem for F diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 2b24eaadc7230..1ce4bfc17f915 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -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); //! @@ -123,17 +123,40 @@ pub use system::*; pub use system_param::*; pub use system_piping::*; +use crate::world::World; + /// Ensure that a given function is a [system](System). /// /// This should be used when writing doc examples, /// to confirm that systems used in an example are /// valid systems. -pub fn assert_is_system>(sys: S) { - if false { - // Check it can be converted into a system - // TODO: This should ensure that the system has no conflicting system params - IntoSystem::into_system(sys); - } +/// +/// # Examples +/// +/// The following example will panic when run since the +/// system's parameters mutably access the same component +/// multiple times. +/// +/// ```should_panic +/// # use bevy_ecs::{prelude::*, system::assert_is_system}; +/// # +/// # #[derive(Component)] +/// # struct Transform; +/// # +/// fn my_system(query1: Query<&mut Transform>, query2: Query<&mut Transform>) { +/// // ... +/// } +/// +/// assert_is_system(my_system); +/// ``` +pub fn assert_is_system( + system: impl IntoSystem, +) { + let mut system = IntoSystem::into_system(system); + + // Initialize the system, which will panic if the system has access conflicts. + let mut world = World::new(); + system.initialize(&mut world); } /// Ensure that a given function is a [read-only system](ReadOnlySystem). @@ -141,15 +164,30 @@ pub fn assert_is_system>(sys: S) /// This should be used when writing doc examples, /// to confirm that systems used in an example are /// valid systems. -pub fn assert_is_read_only_system>(sys: S) +/// +/// # Examples +/// +/// The following example will fail to compile +/// since the system accesses a component mutably. +/// +/// ```compile_fail +/// # use bevy_ecs::{prelude::*, system::assert_is_read_only_system}; +/// # +/// # #[derive(Component)] +/// # struct Transform; +/// # +/// fn my_system(query: Query<&mut Transform>) { +/// // ... +/// } +/// +/// assert_is_read_only_system(my_system); +/// ``` +pub fn assert_is_read_only_system(system: S) where + S: IntoSystem, S::System: ReadOnlySystem, { - if false { - // Check it can be converted into a system - // TODO: This should ensure that the system has no conflicting system params - IntoSystem::into_system(sys); - } + assert_is_system(system); } #[cfg(test)] @@ -214,7 +252,7 @@ mod tests { fn run_system>(world: &mut World, system: S) { let mut schedule = Schedule::default(); - schedule.add_system(system); + schedule.add_systems(system); schedule.run(world); } @@ -276,6 +314,60 @@ mod tests { assert_eq!(*world.resource::(), SystemRan::Yes); } + #[test] + fn get_many_is_ordered() { + use crate::system::Resource; + const ENTITIES_COUNT: usize = 1000; + + #[derive(Resource)] + struct EntitiesArray(Vec); + + fn query_system( + mut ran: ResMut, + entities_array: Res, + q: Query<&W>, + ) { + let entities_array: [Entity; ENTITIES_COUNT] = + entities_array.0.clone().try_into().unwrap(); + + for (i, w) in (0..ENTITIES_COUNT).zip(q.get_many(entities_array).unwrap()) { + assert_eq!(i, w.0); + } + + *ran = SystemRan::Yes; + } + + fn query_system_mut( + mut ran: ResMut, + entities_array: Res, + mut q: Query<&mut W>, + ) { + let entities_array: [Entity; ENTITIES_COUNT] = + entities_array.0.clone().try_into().unwrap(); + + #[allow(unused_mut)] + for (i, mut w) in (0..ENTITIES_COUNT).zip(q.get_many_mut(entities_array).unwrap()) { + assert_eq!(i, w.0); + } + + *ran = SystemRan::Yes; + } + + let mut world = World::default(); + world.insert_resource(SystemRan::No); + let entity_ids = (0..ENTITIES_COUNT) + .map(|i| world.spawn(W(i)).id()) + .collect(); + world.insert_resource(EntitiesArray(entity_ids)); + + run_system(&mut world, query_system); + assert_eq!(*world.resource::(), SystemRan::Yes); + + world.insert_resource(SystemRan::No); + run_system(&mut world, query_system_mut); + assert_eq!(*world.resource::(), SystemRan::Yes); + } + #[test] fn or_param_set_system() { // Regression test for issue #762 diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index e8c54472dae42..4316389adecc1 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -728,6 +728,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { QueryParIter { world: self.world, state: self.state.as_readonly(), + last_run: self.last_run, + this_run: self.this_run, batching_strategy: BatchingStrategy::new(), } } @@ -742,6 +744,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { QueryParIter { world: self.world, state: self.state, + last_run: self.last_run, + this_run: self.this_run, batching_strategy: BatchingStrategy::new(), } } @@ -793,6 +797,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// Returns the read-only query items for the given array of [`Entity`]. /// + /// The returned query items are in the same order as the input. /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. /// The elements of the array do not need to be unique, unlike `get_many_mut`. /// @@ -897,6 +902,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// Returns the query items for the given array of [`Entity`]. /// + /// The returned query items are in the same order as the input. /// In case of a nonexisting entity, duplicate entities or mismatched component, a [`QueryEntityError`] is returned instead. /// /// # See also diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 307dd2bc7da61..b8c11b6262677 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -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. diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 60fc47c56eee2..d0668025c7029 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -89,38 +89,6 @@ use std::{ /// This will most commonly occur when working with `SystemParam`s generically, as the requirement /// has not been proven to the compiler. /// -/// # `!Sync` Resources -/// A `!Sync` type cannot implement `Resource`. However, it is possible to wrap a `Send` but not `Sync` -/// type in [`SyncCell`] or the currently unstable [`Exclusive`] to make it `Sync`. This forces only -/// having mutable access (`&mut T` only, never `&T`), but makes it safe to reference across multiple -/// threads. -/// -/// This will fail to compile since `RefCell` is `!Sync`. -/// ```compile_fail -/// # use std::cell::RefCell; -/// # use bevy_ecs::system::Resource; -/// -/// #[derive(Resource)] -/// struct NotSync { -/// counter: RefCell, -/// } -/// ``` -/// -/// This will compile since the `RefCell` is wrapped with `SyncCell`. -/// ``` -/// # use std::cell::RefCell; -/// # use bevy_ecs::system::Resource; -/// use bevy_utils::synccell::SyncCell; -/// -/// #[derive(Resource)] -/// struct ActuallySync { -/// counter: SyncCell>, -/// } -/// ``` -/// -/// [`SyncCell`]: bevy_utils::synccell::SyncCell -/// [`Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html -/// /// # Safety /// /// The implementor must ensure the following is true. @@ -399,6 +367,38 @@ impl_param_set!(); /// # schedule.add_systems((read_resource_system, write_resource_system).chain()); /// # schedule.run(&mut world); /// ``` +/// +/// # `!Sync` Resources +/// A `!Sync` type cannot implement `Resource`. However, it is possible to wrap a `Send` but not `Sync` +/// type in [`SyncCell`] or the currently unstable [`Exclusive`] to make it `Sync`. This forces only +/// having mutable access (`&mut T` only, never `&T`), but makes it safe to reference across multiple +/// threads. +/// +/// This will fail to compile since `RefCell` is `!Sync`. +/// ```compile_fail +/// # use std::cell::RefCell; +/// # use bevy_ecs::system::Resource; +/// +/// #[derive(Resource)] +/// struct NotSync { +/// counter: RefCell, +/// } +/// ``` +/// +/// This will compile since the `RefCell` is wrapped with `SyncCell`. +/// ``` +/// # use std::cell::RefCell; +/// # use bevy_ecs::system::Resource; +/// use bevy_utils::synccell::SyncCell; +/// +/// #[derive(Resource)] +/// struct ActuallySync { +/// counter: SyncCell>, +/// } +/// ``` +/// +/// [`SyncCell`]: bevy_utils::synccell::SyncCell +/// [`Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html pub trait Resource: Send + Sync + 'static {} // SAFETY: Res only reads a single World resource @@ -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); @@ -1625,7 +1625,7 @@ mod tests { #[derive(SystemParam)] pub struct EncapsulatedParam<'w>(Res<'w, PrivateResource>); - // regression test for https://github.com/bevyengine/bevy/issues/7103. + // Regression test for https://github.com/bevyengine/bevy/issues/7103. #[derive(SystemParam)] pub struct WhereParam<'w, 's, Q> where @@ -1633,4 +1633,13 @@ mod tests { { _q: Query<'w, 's, Q, ()>, } + + // Regression test for https://github.com/bevyengine/bevy/issues/1727. + #[derive(SystemParam)] + pub struct Collide<'w> { + _x: Res<'w, FetchState>, + } + + #[derive(Resource)] + pub struct FetchState; } diff --git a/crates/bevy_ecs/src/system/system_piping.rs b/crates/bevy_ecs/src/system/system_piping.rs index b1551e64f3562..dc1f3dc55a658 100644 --- a/crates/bevy_ecs/src/system/system_piping.rs +++ b/crates/bevy_ecs/src/system/system_piping.rs @@ -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) /// ) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 1511b7fe889b6..39e5b8e40f63b 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1,6 +1,6 @@ use crate::{ archetype::{Archetype, ArchetypeId, Archetypes}, - bundle::{Bundle, BundleInfo}, + bundle::{Bundle, BundleInfo, BundleInserter, DynamicBundle}, change_detection::MutUntyped, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, @@ -279,6 +279,90 @@ impl<'w> EntityMut<'w> { self } + /// Inserts a dynamic [`Component`] into the entity. + /// + /// This will overwrite any previous value(s) of the same component type. + /// + /// You should prefer to use the typed API [`EntityMut::insert`] where possible. + /// + /// # Safety + /// + /// - [`ComponentId`] must be from the same world as [`EntityMut`] + /// - [`OwningPtr`] must be a valid reference to the type represented by [`ComponentId`] + pub unsafe fn insert_by_id( + &mut self, + component_id: ComponentId, + component: OwningPtr<'_>, + ) -> &mut Self { + let change_tick = self.world.change_tick(); + + let bundles = &mut self.world.bundles; + let components = &mut self.world.components; + + let (bundle_info, storage_type) = bundles.init_component_info(components, component_id); + let bundle_inserter = bundle_info.get_bundle_inserter( + &mut self.world.entities, + &mut self.world.archetypes, + &mut self.world.components, + &mut self.world.storages, + self.location.archetype_id, + change_tick, + ); + + self.location = insert_dynamic_bundle( + bundle_inserter, + self.entity, + self.location, + Some(component).into_iter(), + Some(storage_type).into_iter(), + ); + + self + } + + /// Inserts a dynamic [`Bundle`] into the entity. + /// + /// This will overwrite any previous value(s) of the same component type. + /// + /// You should prefer to use the typed API [`EntityMut::insert`] where possible. + /// If your [`Bundle`] only has one component, use the cached API [`EntityMut::insert_by_id`]. + /// + /// If possible, pass a sorted slice of `ComponentId` to maximize caching potential. + /// + /// # Safety + /// - Each [`ComponentId`] must be from the same world as [`EntityMut`] + /// - Each [`OwningPtr`] must be a valid reference to the type represented by [`ComponentId`] + pub unsafe fn insert_by_ids<'a, I: Iterator>>( + &mut self, + component_ids: &[ComponentId], + iter_components: I, + ) -> &mut Self { + let change_tick = self.world.change_tick(); + + let bundles = &mut self.world.bundles; + let components = &mut self.world.components; + + let (bundle_info, storage_types) = bundles.init_dynamic_info(components, component_ids); + let bundle_inserter = bundle_info.get_bundle_inserter( + &mut self.world.entities, + &mut self.world.archetypes, + &mut self.world.components, + &mut self.world.storages, + self.location.archetype_id, + change_tick, + ); + + self.location = insert_dynamic_bundle( + bundle_inserter, + self.entity, + self.location, + iter_components, + storage_types.iter().cloned(), + ); + + self + } + /// Removes all components in the [`Bundle`] from the entity and returns their previous values. /// /// **Note:** If the entity does not have every component in the bundle, this method will not @@ -311,7 +395,7 @@ impl<'w> EntityMut<'w> { return None; } - let mut bundle_components = bundle_info.component_ids.iter().cloned(); + let mut bundle_components = bundle_info.components().iter().cloned(); let entity = self.entity; // SAFETY: bundle components are iterated in order, which guarantees that the component type // matches @@ -463,7 +547,7 @@ impl<'w> EntityMut<'w> { let old_archetype = &mut archetypes[old_location.archetype_id]; let entity = self.entity; - for component_id in bundle_info.component_ids.iter().cloned() { + for component_id in bundle_info.components().iter().cloned() { if old_archetype.contains(component_id) { removed_components.send(component_id, entity); @@ -603,7 +687,7 @@ impl<'w> EntityMut<'w> { /// // Mutate the world while we have access to it. /// let mut r = world.resource_mut::(); /// r.0 += 1; - /// + /// /// // Return a value from the world before giving it back to the `EntityMut`. /// *r /// }); @@ -672,6 +756,44 @@ impl<'w> EntityMut<'w> { } } +/// Inserts a dynamic [`Bundle`] into the entity. +/// +/// # Safety +/// +/// - [`OwningPtr`] and [`StorageType`] iterators must correspond to the +/// [`BundleInfo`] used to construct [`BundleInserter`] +/// - [`Entity`] must correspond to [`EntityLocation`] +unsafe fn insert_dynamic_bundle< + 'a, + I: Iterator>, + S: Iterator, +>( + mut bundle_inserter: BundleInserter<'_, '_>, + entity: Entity, + location: EntityLocation, + components: I, + storage_types: S, +) -> EntityLocation { + struct DynamicInsertBundle<'a, I: Iterator)>> { + components: I, + } + + impl<'a, I: Iterator)>> DynamicBundle + for DynamicInsertBundle<'a, I> + { + fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) { + self.components.for_each(|(t, ptr)| func(t, ptr)); + } + } + + let bundle = DynamicInsertBundle { + components: storage_types.zip(components), + }; + + // SAFETY: location matches current entity. + unsafe { bundle_inserter.insert(entity, location, bundle) } +} + /// Removes a bundle from the given archetype and returns the resulting archetype (or None if the /// removal was invalid). in the event that adding the given bundle does not result in an Archetype /// change. Results are cached in the Archetype Graph to avoid redundant work. @@ -694,9 +816,11 @@ unsafe fn remove_bundle_from_archetype( let remove_bundle_result = { let current_archetype = &mut archetypes[archetype_id]; if intersection { - current_archetype.edges().get_remove_bundle(bundle_info.id) + current_archetype + .edges() + .get_remove_bundle(bundle_info.id()) } else { - current_archetype.edges().get_take_bundle(bundle_info.id) + current_archetype.edges().get_take_bundle(bundle_info.id()) } }; let result = if let Some(result) = remove_bundle_result { @@ -710,7 +834,7 @@ unsafe fn remove_bundle_from_archetype( let current_archetype = &mut archetypes[archetype_id]; let mut removed_table_components = Vec::new(); let mut removed_sparse_set_components = Vec::new(); - for component_id in bundle_info.component_ids.iter().cloned() { + for component_id in bundle_info.components().iter().cloned() { if current_archetype.contains(component_id) { // SAFETY: bundle components were already initialized by bundles.get_info let component_info = components.get_info_unchecked(component_id); @@ -724,7 +848,7 @@ unsafe fn remove_bundle_from_archetype( // graph current_archetype .edges_mut() - .insert_take_bundle(bundle_info.id, None); + .insert_take_bundle(bundle_info.id(), None); return None; } } @@ -763,11 +887,11 @@ unsafe fn remove_bundle_from_archetype( if intersection { current_archetype .edges_mut() - .insert_remove_bundle(bundle_info.id, result); + .insert_remove_bundle(bundle_info.id(), result); } else { current_archetype .edges_mut() - .insert_take_bundle(bundle_info.id, result); + .insert_take_bundle(bundle_info.id(), result); } result } @@ -833,6 +957,7 @@ pub(crate) unsafe fn take_component<'a>( #[cfg(test)] mod tests { + use bevy_ptr::OwningPtr; use std::panic::AssertUnwindSafe; use crate as bevy_ecs; @@ -860,9 +985,13 @@ mod tests { assert_eq!(a, vec![1]); } - #[derive(Component)] + #[derive(Component, Clone, Copy, Debug, PartialEq)] struct TestComponent(u32); + #[derive(Component, Clone, Copy, Debug, PartialEq)] + #[component(storage = "SparseSet")] + struct TestComponent2(u32); + #[test] fn entity_ref_get_by_id() { let mut world = World::new(); @@ -1105,4 +1234,72 @@ mod tests { assert_eq!(world.entity(e2).get::().unwrap(), &Dense(1)); } + + #[test] + fn entity_mut_insert_by_id() { + let mut world = World::new(); + let test_component_id = world.init_component::(); + + let mut entity = world.spawn_empty(); + OwningPtr::make(TestComponent(42), |ptr| { + // SAFETY: `ptr` matches the component id + unsafe { entity.insert_by_id(test_component_id, ptr) }; + }); + + let components: Vec<_> = world.query::<&TestComponent>().iter(&world).collect(); + + assert_eq!(components, vec![&TestComponent(42)]); + + // Compare with `insert_bundle_by_id` + + let mut entity = world.spawn_empty(); + OwningPtr::make(TestComponent(84), |ptr| { + // SAFETY: `ptr` matches the component id + unsafe { entity.insert_by_ids(&[test_component_id], vec![ptr].into_iter()) }; + }); + + let components: Vec<_> = world.query::<&TestComponent>().iter(&world).collect(); + + assert_eq!(components, vec![&TestComponent(42), &TestComponent(84)]); + } + + #[test] + fn entity_mut_insert_bundle_by_id() { + let mut world = World::new(); + let test_component_id = world.init_component::(); + let test_component_2_id = world.init_component::(); + + let component_ids = [test_component_id, test_component_2_id]; + let test_component_value = TestComponent(42); + let test_component_2_value = TestComponent2(84); + + let mut entity = world.spawn_empty(); + OwningPtr::make(test_component_value, |ptr1| { + OwningPtr::make(test_component_2_value, |ptr2| { + // SAFETY: `ptr1` and `ptr2` match the component ids + unsafe { entity.insert_by_ids(&component_ids, vec![ptr1, ptr2].into_iter()) }; + }); + }); + + let dynamic_components: Vec<_> = world + .query::<(&TestComponent, &TestComponent2)>() + .iter(&world) + .collect(); + + assert_eq!( + dynamic_components, + vec![(&TestComponent(42), &TestComponent2(84))] + ); + + // Compare with `World` generated using static type equivalents + let mut static_world = World::new(); + + static_world.spawn((test_component_value, test_component_2_value)); + let static_components: Vec<_> = static_world + .query::<(&TestComponent, &TestComponent2)>() + .iter(&static_world) + .collect(); + + assert_eq!(dynamic_components, static_components); + } } diff --git a/crates/bevy_ecs/src/world/error.rs b/crates/bevy_ecs/src/world/error.rs new file mode 100644 index 0000000000000..a6197ac44c36d --- /dev/null +++ b/crates/bevy_ecs/src/world/error.rs @@ -0,0 +1,10 @@ +use thiserror::Error; + +use crate::schedule::BoxedScheduleLabel; + +/// The error type returned by [`World::try_run_schedule`] if the provided schedule does not exist. +/// +/// [`World::try_run_schedule`]: crate::world::World::try_run_schedule +#[derive(Error, Debug)] +#[error("The schedule with the label {0:?} was not found.")] +pub struct TryRunScheduleError(pub BoxedScheduleLabel); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 0832141f4bf02..288650a1b951c 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1,4 +1,5 @@ mod entity_ref; +pub mod error; mod spawn_batch; pub mod unsafe_world_cell; mod world_cell; @@ -20,6 +21,7 @@ use crate::{ schedule::{Schedule, ScheduleLabel, Schedules}, storage::{ResourceData, Storages}, system::Resource, + world::error::TryRunScheduleError, }; use bevy_ptr::{OwningPtr, Ptr}; use bevy_utils::tracing::warn; @@ -1714,6 +1716,47 @@ impl World { schedules.insert(label, schedule); } + /// Attempts to run the [`Schedule`] associated with the `label` a single time, + /// and returns a [`TryRunScheduleError`] if the schedule does not exist. + /// + /// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label, + /// and system state is cached. + /// + /// For simple testing use cases, call [`Schedule::run(&mut world)`](Schedule::run) instead. + pub fn try_run_schedule( + &mut self, + label: impl ScheduleLabel, + ) -> Result<(), TryRunScheduleError> { + self.try_run_schedule_ref(&label) + } + + /// Attempts to run the [`Schedule`] associated with the `label` a single time, + /// and returns a [`TryRunScheduleError`] if the schedule does not exist. + /// + /// Unlike the `try_run_schedule` method, this method takes the label by reference, which can save a clone. + /// + /// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label, + /// and system state is cached. + /// + /// For simple testing use cases, call [`Schedule::run(&mut world)`](Schedule::run) instead. + pub fn try_run_schedule_ref( + &mut self, + label: &dyn ScheduleLabel, + ) -> Result<(), TryRunScheduleError> { + let Some((extracted_label, mut schedule)) = self.resource_mut::().remove_entry(label) else { + return Err(TryRunScheduleError(label.dyn_clone())); + }; + + // TODO: move this span to Schedule::run + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!("schedule", name = ?extracted_label).entered(); + schedule.run(self); + self.resource_mut::() + .insert(extracted_label, schedule); + + Ok(()) + } + /// Runs the [`Schedule`] associated with the `label` a single time. /// /// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label, @@ -1741,17 +1784,8 @@ impl World { /// /// Panics if the requested schedule does not exist, or the [`Schedules`] resource was not added. pub fn run_schedule_ref(&mut self, label: &dyn ScheduleLabel) { - let (extracted_label, mut schedule) = self - .resource_mut::() - .remove_entry(label) - .unwrap_or_else(|| panic!("The schedule with the label {label:?} was not found.")); - - // TODO: move this span to Schedule::run - #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!("schedule", name = ?extracted_label).entered(); - schedule.run(self); - self.resource_mut::() - .insert(extracted_label, schedule); + self.try_run_schedule_ref(label) + .unwrap_or_else(|e| panic!("{}", e)); } } diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index f01171010034f..abbf34307c646 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -1,7 +1,7 @@ mod converter; mod gilrs_system; -use bevy_app::{App, 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), } diff --git a/crates/bevy_gizmos/Cargo.toml b/crates/bevy_gizmos/Cargo.toml new file mode 100644 index 0000000000000..cee285784ca69 --- /dev/null +++ b/crates/bevy_gizmos/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "bevy_gizmos" +version = "0.11.0-dev" +edition = "2021" +description = "Provides gizmos for Bevy Engine" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy"] + +[dependencies] +# Bevy +bevy_pbr = { path = "../bevy_pbr", version = "0.11.0-dev", optional = true } +bevy_sprite = { path = "../bevy_sprite", version = "0.11.0-dev", optional = true } +bevy_app = { path = "../bevy_app", version = "0.11.0-dev" } +bevy_ecs = { path = "../bevy_ecs", version = "0.11.0-dev" } +bevy_math = { path = "../bevy_math", version = "0.11.0-dev" } +bevy_asset = { path = "../bevy_asset", version = "0.11.0-dev" } +bevy_render = { path = "../bevy_render", version = "0.11.0-dev" } +bevy_utils = { path = "../bevy_utils", version = "0.11.0-dev" } +bevy_core = { path = "../bevy_core", version = "0.11.0-dev" } +bevy_reflect = { path = "../bevy_reflect", version = "0.11.0-dev" } +bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.11.0-dev" } diff --git a/crates/bevy_gizmos/src/gizmos.rs b/crates/bevy_gizmos/src/gizmos.rs new file mode 100644 index 0000000000000..fa37416db83a6 --- /dev/null +++ b/crates/bevy_gizmos/src/gizmos.rs @@ -0,0 +1,349 @@ +use std::{f32::consts::TAU, iter}; + +use bevy_ecs::{ + system::{Deferred, Resource, SystemBuffer, SystemMeta}, + world::World, +}; +use bevy_math::{Mat2, Quat, Vec2, Vec3}; +use bevy_render::prelude::Color; + +type PositionItem = [f32; 3]; +type ColorItem = [f32; 4]; + +const DEFAULT_CIRCLE_SEGMENTS: usize = 32; + +#[derive(Resource, Default)] +pub(crate) struct GizmoStorage { + pub list_positions: Vec, + pub list_colors: Vec, + pub strip_positions: Vec, + pub strip_colors: Vec, +} + +pub type Gizmos<'s> = Deferred<'s, GizmoBuffer>; + +#[derive(Default)] +pub struct GizmoBuffer { + list_positions: Vec, + list_colors: Vec, + strip_positions: Vec, + strip_colors: Vec, +} + +impl SystemBuffer for GizmoBuffer { + fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) { + let mut storage = world.resource_mut::(); + storage.list_positions.append(&mut self.list_positions); + storage.list_colors.append(&mut self.list_colors); + storage.strip_positions.append(&mut self.strip_positions); + storage.strip_colors.append(&mut self.strip_colors); + } +} + +impl GizmoBuffer { + #[inline] + pub fn line(&mut self, start: Vec3, end: Vec3, color: Color) { + self.extend_list_positions([start, end]); + self.add_list_color(color, 2); + } + + /// Draw a line from `start` to `end`. + #[inline] + pub fn line_gradient(&mut self, start: Vec3, end: Vec3, start_color: Color, end_color: Color) { + self.extend_list_positions([start, end]); + self.extend_list_colors([start_color, end_color]); + } + + /// Draw a line from `start` to `start + vector`. + #[inline] + pub fn ray(&mut self, start: Vec3, vector: Vec3, color: Color) { + self.line(start, start + vector, color); + } + + /// Draw a line from `start` to `start + vector`. + #[inline] + pub fn ray_gradient( + &mut self, + start: Vec3, + vector: Vec3, + start_color: Color, + end_color: Color, + ) { + self.line_gradient(start, start + vector, start_color, end_color); + } + + #[inline] + pub fn linestrip(&mut self, positions: impl IntoIterator, color: Color) { + self.extend_strip_positions(positions.into_iter()); + self.strip_colors + .resize(self.strip_positions.len() - 1, color.as_linear_rgba_f32()); + self.strip_colors.push([f32::NAN; 4]); + } + + #[inline] + pub fn linestrip_gradient(&mut self, points: impl IntoIterator) { + let points = points.into_iter(); + + let (min, _) = points.size_hint(); + self.strip_positions.reserve(min); + self.strip_colors.reserve(min); + + for (position, color) in points { + self.strip_positions.push(position.to_array()); + self.strip_colors.push(color.as_linear_rgba_f32()); + } + + self.strip_positions.push([f32::NAN; 3]); + self.strip_colors.push([f32::NAN; 4]); + } + + /// Draw a circle at `position` with the flat side facing `normal`. + #[inline] + pub fn circle( + &mut self, + position: Vec3, + normal: Vec3, + radius: f32, + color: Color, + ) -> CircleBuilder { + CircleBuilder { + buffer: self, + position, + normal, + radius, + color, + segments: DEFAULT_CIRCLE_SEGMENTS, + } + } + + /// Draw a sphere. + #[inline] + pub fn sphere( + &mut self, + position: Vec3, + rotation: Quat, + radius: f32, + color: Color, + ) -> SphereBuilder { + SphereBuilder { + buffer: self, + position, + rotation, + radius, + color, + circle_segments: DEFAULT_CIRCLE_SEGMENTS, + } + } + + /// Draw a rectangle. + #[inline] + pub fn rect(&mut self, position: Vec3, rotation: Quat, size: Vec2, color: Color) { + let [tl, tr, br, bl] = rect_inner(size).map(|vec2| position + rotation * vec2.extend(0.)); + self.linestrip([tl, tr, br, bl, tl], color); + } + + /// Draw a box. + #[inline] + pub fn cuboid(&mut self, position: Vec3, rotation: Quat, size: Vec3, color: Color) { + let rect = rect_inner(size.truncate()); + // Front + let [tlf, trf, brf, blf] = rect.map(|vec2| position + rotation * vec2.extend(size.z / 2.)); + // Back + let [tlb, trb, brb, blb] = rect.map(|vec2| position + rotation * vec2.extend(-size.z / 2.)); + + let positions = [ + tlf, trf, trf, brf, brf, blf, blf, tlf, // Front + tlb, trb, trb, brb, brb, blb, blb, tlb, // Back + tlf, tlb, trf, trb, brf, brb, blf, blb, // Front to back + ]; + self.extend_list_positions(positions); + self.add_list_color(color, 24); + } + + /// Draw a line from `start` to `end`. + #[inline] + pub fn line_2d(&mut self, start: Vec2, end: Vec2, color: Color) { + self.line(start.extend(0.), end.extend(0.), color); + } + + /// Draw a line from `start` to `end`. + #[inline] + pub fn line_gradient_2d( + &mut self, + start: Vec2, + end: Vec2, + start_color: Color, + end_color: Color, + ) { + self.line_gradient(start.extend(0.), end.extend(0.), start_color, end_color); + } + + #[inline] + pub fn linestrip_2d(&mut self, positions: impl IntoIterator, color: Color) { + self.linestrip(positions.into_iter().map(|vec2| vec2.extend(0.)), color); + } + + #[inline] + pub fn linestrip_gradient_2d(&mut self, positions: impl IntoIterator) { + self.linestrip_gradient( + positions + .into_iter() + .map(|(vec2, color)| (vec2.extend(0.), color)), + ); + } + + /// Draw a line from `start` to `start + vector`. + #[inline] + pub fn ray_2d(&mut self, start: Vec2, vector: Vec2, color: Color) { + self.line_2d(start, start + vector, color); + } + + /// Draw a line from `start` to `start + vector`. + #[inline] + pub fn ray_gradient_2d( + &mut self, + start: Vec2, + vector: Vec2, + start_color: Color, + end_color: Color, + ) { + self.line_gradient_2d(start, start + vector, start_color, end_color); + } + + // Draw a circle. + #[inline] + pub fn circle_2d(&mut self, position: Vec2, radius: f32, color: Color) -> Circle2dBuilder { + Circle2dBuilder { + buffer: self, + position, + radius, + color, + segments: DEFAULT_CIRCLE_SEGMENTS, + } + } + + /// Draw a rectangle. + #[inline] + pub fn rect_2d(&mut self, position: Vec2, rotation: f32, size: Vec2, color: Color) { + let rotation = Mat2::from_angle(rotation); + let [tl, tr, br, bl] = rect_inner(size).map(|vec2| position + rotation * vec2); + self.linestrip_2d([tl, tr, br, bl, tl], color); + } + + #[inline] + fn extend_list_positions(&mut self, positions: impl IntoIterator) { + self.list_positions + .extend(positions.into_iter().map(|vec3| vec3.to_array())); + } + + #[inline] + fn extend_list_colors(&mut self, colors: impl IntoIterator) { + self.list_colors + .extend(colors.into_iter().map(|color| color.as_linear_rgba_f32())); + } + + #[inline] + fn add_list_color(&mut self, color: Color, count: usize) { + self.list_colors + .extend(iter::repeat(color.as_linear_rgba_f32()).take(count)); + } + + #[inline] + fn extend_strip_positions(&mut self, positions: impl IntoIterator) { + self.strip_positions.extend( + positions + .into_iter() + .map(|vec3| vec3.to_array()) + .chain(iter::once([f32::NAN; 3])), + ); + } +} + +pub struct CircleBuilder<'a> { + buffer: &'a mut GizmoBuffer, + position: Vec3, + normal: Vec3, + radius: f32, + color: Color, + segments: usize, +} + +impl<'a> CircleBuilder<'a> { + pub fn segments(mut self, segments: usize) -> Self { + self.segments = segments; + self + } +} + +impl<'a> Drop for CircleBuilder<'a> { + fn drop(&mut self) { + let rotation = Quat::from_rotation_arc(Vec3::Z, self.normal); + let positions = circle_inner(self.radius, self.segments) + .map(|vec2| (self.position + rotation * vec2.extend(0.))); + self.buffer.linestrip(positions, self.color); + } +} + +pub struct SphereBuilder<'a> { + buffer: &'a mut GizmoBuffer, + position: Vec3, + rotation: Quat, + radius: f32, + color: Color, + circle_segments: usize, +} + +impl SphereBuilder<'_> { + pub fn circle_segments(mut self, segments: usize) -> Self { + self.circle_segments = segments; + self + } +} + +impl Drop for SphereBuilder<'_> { + fn drop(&mut self) { + for axis in Vec3::AXES { + self.buffer + .circle(self.position, self.rotation * axis, self.radius, self.color) + .segments(self.circle_segments); + } + } +} + +pub struct Circle2dBuilder<'a> { + buffer: &'a mut GizmoBuffer, + position: Vec2, + radius: f32, + color: Color, + segments: usize, +} + +impl Circle2dBuilder<'_> { + pub fn segments(mut self, segments: usize) -> Self { + self.segments = segments; + self + } +} + +impl Drop for Circle2dBuilder<'_> { + fn drop(&mut self) { + let positions = circle_inner(self.radius, self.segments).map(|vec2| (vec2 + self.position)); + self.buffer.linestrip_2d(positions, self.color); + } +} + +fn circle_inner(radius: f32, segments: usize) -> impl Iterator { + (0..segments + 1).map(move |i| { + let angle = i as f32 * TAU / segments as f32; + Vec2::from(angle.sin_cos()) * radius + }) +} + +fn rect_inner(size: Vec2) -> [Vec2; 4] { + let half_size = size / 2.; + let tl = Vec2::new(-half_size.x, half_size.y); + let tr = Vec2::new(half_size.x, half_size.y); + let bl = Vec2::new(-half_size.x, -half_size.y); + let br = Vec2::new(half_size.x, -half_size.y); + [tl, tr, br, bl] +} diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs new file mode 100644 index 0000000000000..9d609c05a6db9 --- /dev/null +++ b/crates/bevy_gizmos/src/lib.rs @@ -0,0 +1,187 @@ +use std::mem; + +use bevy_app::{Last, Plugin}; +use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped}; +use bevy_ecs::{ + prelude::{Component, DetectChanges}, + schedule::IntoSystemConfigs, + system::{Commands, Res, ResMut, Resource}, + world::{FromWorld, World}, +}; +use bevy_math::Mat4; +use bevy_reflect::TypeUuid; +use bevy_render::{ + mesh::Mesh, + render_phase::AddRenderCommand, + render_resource::{PrimitiveTopology, Shader, SpecializedMeshPipelines}, + Extract, ExtractSchedule, Render, RenderApp, RenderSet, +}; + +#[cfg(feature = "bevy_pbr")] +use bevy_pbr::MeshUniform; +#[cfg(feature = "bevy_sprite")] +use bevy_sprite::{Mesh2dHandle, Mesh2dUniform}; + +pub mod gizmos; + +#[cfg(feature = "bevy_sprite")] +mod pipeline_2d; +#[cfg(feature = "bevy_pbr")] +mod pipeline_3d; + +use crate::gizmos::GizmoStorage; + +/// The `bevy_gizmos` prelude. +pub mod prelude { + #[doc(hidden)] + pub use crate::{gizmos::Gizmos, GizmoConfig}; +} + +const LINE_SHADER_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 7414812689238026784); + +pub struct GizmoPlugin; + +impl Plugin for GizmoPlugin { + fn build(&self, app: &mut bevy_app::App) { + load_internal_asset!(app, LINE_SHADER_HANDLE, "lines.wgsl", Shader::from_wgsl); + + app.init_resource::() + .init_resource::() + .init_resource::() + .add_systems(Last, update_gizmo_meshes); + + let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; + + render_app.add_systems(ExtractSchedule, extract_gizmo_data); + + #[cfg(feature = "bevy_sprite")] + { + use bevy_core_pipeline::core_2d::Transparent2d; + use pipeline_2d::*; + + render_app + .add_render_command::() + .init_resource::() + .init_resource::>() + .add_systems(Render, queue_gizmos_2d.in_set(RenderSet::Queue)); + } + + #[cfg(feature = "bevy_pbr")] + { + use bevy_core_pipeline::core_3d::Opaque3d; + use pipeline_3d::*; + + render_app + .add_render_command::() + .init_resource::() + .init_resource::>() + .add_systems(Render, queue_gizmos_3d.in_set(RenderSet::Queue)); + } + } +} + +#[derive(Resource, Clone, Copy)] +pub struct GizmoConfig { + /// Set to `false` to stop drawing gizmos. + /// + /// Defaults to `true`. + pub enabled: bool, + /// Draw gizmos on top of everything else, ignoring depth. + /// + /// This setting only affects 3D. In 2D, gizmos are always drawn on top. + /// + /// Defaults to `false`. + pub on_top: bool, +} + +impl Default for GizmoConfig { + fn default() -> Self { + Self { + enabled: true, + on_top: false, + } + } +} + +#[derive(Resource)] +struct MeshHandles { + list: Handle, + strip: Handle, +} + +impl FromWorld for MeshHandles { + fn from_world(world: &mut World) -> Self { + let mut meshes = world.resource_mut::>(); + + MeshHandles { + list: meshes.add(Mesh::new(PrimitiveTopology::LineList)), + strip: meshes.add(Mesh::new(PrimitiveTopology::LineStrip)), + } + } +} + +#[derive(Component)] +struct GizmoMesh; + +fn update_gizmo_meshes( + mut meshes: ResMut>, + handles: Res, + mut storage: ResMut, +) { + let list_mesh = meshes.get_mut(&handles.list).unwrap(); + + let positions = mem::take(&mut storage.list_positions); + list_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); + + let colors = mem::take(&mut storage.list_colors); + list_mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors); + + let strip_mesh = meshes.get_mut(&handles.strip).unwrap(); + + let positions = mem::take(&mut storage.strip_positions); + strip_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); + + let colors = mem::take(&mut storage.strip_colors); + strip_mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors); +} + +fn extract_gizmo_data( + mut commands: Commands, + handles: Extract>, + config: Extract>, +) { + if config.is_changed() { + commands.insert_resource(**config); + } + + if !config.enabled { + return; + } + + let transform = Mat4::IDENTITY; + let inverse_transpose_model = transform.inverse().transpose(); + commands.spawn_batch([&handles.list, &handles.strip].map(|handle| { + ( + GizmoMesh, + #[cfg(feature = "bevy_pbr")] + ( + handle.clone(), + MeshUniform { + flags: 0, + transform, + inverse_transpose_model, + }, + ), + #[cfg(feature = "bevy_sprite")] + ( + Mesh2dHandle(handle.clone()), + Mesh2dUniform { + flags: 0, + transform, + inverse_transpose_model, + }, + ), + ) + })); +} diff --git a/crates/bevy_gizmos/src/lines.wgsl b/crates/bevy_gizmos/src/lines.wgsl new file mode 100644 index 0000000000000..9fa8244f178e7 --- /dev/null +++ b/crates/bevy_gizmos/src/lines.wgsl @@ -0,0 +1,44 @@ +#ifdef GIZMO_LINES_3D + #import bevy_pbr::mesh_view_bindings +#else + #import bevy_sprite::mesh2d_view_bindings +#endif + +struct VertexInput { + @location(0) pos: vec3, + @location(1) color: vec4, +} + +struct VertexOutput { + @builtin(position) pos: vec4, + @location(0) color: vec4, +} + +struct FragmentOutput { + @builtin(frag_depth) depth: f32, + @location(0) color: vec4, +} + +@vertex +fn vertex(in: VertexInput) -> VertexOutput { + var out: VertexOutput; + + out.pos = view.view_proj * vec4(in.pos, 1.0); + out.color = in.color; + + return out; +} + +@fragment +fn fragment(in: VertexOutput) -> FragmentOutput { + var out: FragmentOutput; + +#ifdef DEPTH_TEST + out.depth = in.pos.z; +#else + out.depth = 1.0; +#endif + + out.color = in.color; + return out; +} diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/pipeline_2d.rs new file mode 100644 index 0000000000000..f3ab5be25cba8 --- /dev/null +++ b/crates/bevy_gizmos/src/pipeline_2d.rs @@ -0,0 +1,128 @@ +use bevy_asset::Handle; +use bevy_core_pipeline::core_2d::Transparent2d; +use bevy_ecs::{ + prelude::Entity, + query::With, + system::{Query, Res, ResMut, Resource}, + world::{FromWorld, World}, +}; +use bevy_render::{ + mesh::{Mesh, MeshVertexBufferLayout}, + render_asset::RenderAssets, + render_phase::{DrawFunctions, RenderPhase, SetItemPipeline}, + render_resource::*, + texture::BevyDefault, + view::{ExtractedView, Msaa, ViewTarget}, +}; +use bevy_sprite::*; +use bevy_utils::FloatOrd; + +use crate::{GizmoMesh, LINE_SHADER_HANDLE}; + +#[derive(Resource)] +pub(crate) struct GizmoLinePipeline { + mesh_pipeline: Mesh2dPipeline, + shader: Handle, +} + +impl FromWorld for GizmoLinePipeline { + fn from_world(render_world: &mut World) -> Self { + GizmoLinePipeline { + mesh_pipeline: render_world.resource::().clone(), + shader: LINE_SHADER_HANDLE.typed(), + } + } +} + +impl SpecializedMeshPipeline for GizmoLinePipeline { + type Key = Mesh2dPipelineKey; + + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayout, + ) -> Result { + let vertex_buffer_layout = layout.get_layout(&[ + Mesh::ATTRIBUTE_POSITION.at_shader_location(0), + Mesh::ATTRIBUTE_COLOR.at_shader_location(1), + ])?; + + let format = if key.contains(Mesh2dPipelineKey::HDR) { + ViewTarget::TEXTURE_FORMAT_HDR + } else { + TextureFormat::bevy_default() + }; + + Ok(RenderPipelineDescriptor { + vertex: VertexState { + shader: self.shader.clone_weak(), + entry_point: "vertex".into(), + shader_defs: vec![], + buffers: vec![vertex_buffer_layout], + }, + fragment: Some(FragmentState { + shader: self.shader.clone_weak(), + shader_defs: vec![], + entry_point: "fragment".into(), + targets: vec![Some(ColorTargetState { + format, + blend: Some(BlendState::ALPHA_BLENDING), + write_mask: ColorWrites::ALL, + })], + }), + layout: vec![self.mesh_pipeline.view_layout.clone()], + primitive: PrimitiveState { + topology: key.primitive_topology(), + ..Default::default() + }, + depth_stencil: None, + multisample: MultisampleState { + count: key.msaa_samples(), + mask: !0, + alpha_to_coverage_enabled: false, + }, + push_constant_ranges: vec![], + label: Some("gizmo_2d_pipeline".into()), + }) + } +} + +pub(crate) type DrawGizmoLines = ( + SetItemPipeline, + SetMesh2dViewBindGroup<0>, + SetMesh2dBindGroup<1>, + DrawMesh2d, +); + +#[allow(clippy::too_many_arguments)] +pub(crate) fn queue_gizmos_2d( + draw_functions: Res>, + pipeline: Res, + pipeline_cache: Res, + mut specialized_pipelines: ResMut>, + gpu_meshes: Res>, + msaa: Res, + mesh_handles: Query<(Entity, &Mesh2dHandle), With>, + mut views: Query<(&ExtractedView, &mut RenderPhase)>, +) { + let draw_function = draw_functions.read().get_id::().unwrap(); + let key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()); + for (view, mut phase) in &mut views { + let key = key | Mesh2dPipelineKey::from_hdr(view.hdr); + for (entity, mesh_handle) in &mesh_handles { + let Some(mesh) = gpu_meshes.get(&mesh_handle.0) else { continue; }; + + let key = key | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology); + let pipeline = specialized_pipelines + .specialize(&pipeline_cache, &pipeline, key, &mesh.layout) + .unwrap(); + phase.add(Transparent2d { + entity, + draw_function, + pipeline, + sort_key: FloatOrd(f32::MAX), + batch_range: None, + }); + } + } +} diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs new file mode 100644 index 0000000000000..6064a60a566de --- /dev/null +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -0,0 +1,164 @@ +use bevy_asset::Handle; +use bevy_core_pipeline::core_3d::Opaque3d; +use bevy_ecs::{ + entity::Entity, + query::With, + system::{Query, Res, ResMut, Resource}, + world::{FromWorld, World}, +}; +use bevy_pbr::*; +use bevy_render::{ + mesh::Mesh, + render_resource::Shader, + view::{ExtractedView, ViewTarget}, +}; +use bevy_render::{ + mesh::MeshVertexBufferLayout, + render_asset::RenderAssets, + render_phase::{DrawFunctions, RenderPhase, SetItemPipeline}, + render_resource::*, + texture::BevyDefault, + view::Msaa, +}; + +use crate::{GizmoConfig, GizmoMesh, LINE_SHADER_HANDLE}; + +#[derive(Resource)] +pub(crate) struct GizmoPipeline { + mesh_pipeline: MeshPipeline, + shader: Handle, +} + +impl FromWorld for GizmoPipeline { + fn from_world(render_world: &mut World) -> Self { + GizmoPipeline { + mesh_pipeline: render_world.resource::().clone(), + shader: LINE_SHADER_HANDLE.typed(), + } + } +} + +impl SpecializedMeshPipeline for GizmoPipeline { + type Key = (bool, MeshPipelineKey); + + fn specialize( + &self, + (depth_test, key): Self::Key, + layout: &MeshVertexBufferLayout, + ) -> Result { + let mut shader_defs = Vec::new(); + shader_defs.push("GIZMO_LINES_3D".into()); + shader_defs.push(ShaderDefVal::Int( + "MAX_DIRECTIONAL_LIGHTS".to_string(), + MAX_DIRECTIONAL_LIGHTS as i32, + )); + shader_defs.push(ShaderDefVal::Int( + "MAX_CASCADES_PER_LIGHT".to_string(), + MAX_CASCADES_PER_LIGHT as i32, + )); + if depth_test { + shader_defs.push("DEPTH_TEST".into()); + } + + let vertex_buffer_layout = layout.get_layout(&[ + Mesh::ATTRIBUTE_POSITION.at_shader_location(0), + Mesh::ATTRIBUTE_COLOR.at_shader_location(1), + ])?; + let bind_group_layout = match key.msaa_samples() { + 1 => vec![self.mesh_pipeline.view_layout.clone()], + _ => { + shader_defs.push("MULTISAMPLED".into()); + vec![self.mesh_pipeline.view_layout_multisampled.clone()] + } + }; + + let format = if key.contains(MeshPipelineKey::HDR) { + ViewTarget::TEXTURE_FORMAT_HDR + } else { + TextureFormat::bevy_default() + }; + + Ok(RenderPipelineDescriptor { + vertex: VertexState { + shader: self.shader.clone_weak(), + entry_point: "vertex".into(), + shader_defs: shader_defs.clone(), + buffers: vec![vertex_buffer_layout], + }, + fragment: Some(FragmentState { + shader: self.shader.clone_weak(), + shader_defs, + entry_point: "fragment".into(), + targets: vec![Some(ColorTargetState { + format, + blend: None, + write_mask: ColorWrites::ALL, + })], + }), + layout: bind_group_layout, + primitive: PrimitiveState { + topology: key.primitive_topology(), + ..Default::default() + }, + depth_stencil: Some(DepthStencilState { + format: TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: CompareFunction::Greater, + stencil: Default::default(), + bias: Default::default(), + }), + multisample: MultisampleState { + count: key.msaa_samples(), + mask: !0, + alpha_to_coverage_enabled: false, + }, + push_constant_ranges: vec![], + label: Some("gizmo_3d_pipeline".into()), + }) + } +} + +pub(crate) type DrawGizmoLines = ( + SetItemPipeline, + SetMeshViewBindGroup<0>, + SetMeshBindGroup<1>, + DrawMesh, +); + +#[allow(clippy::too_many_arguments)] +pub(crate) fn queue_gizmos_3d( + draw_functions: Res>, + pipeline: Res, + mut pipelines: ResMut>, + pipeline_cache: Res, + render_meshes: Res>, + msaa: Res, + mesh_handles: Query<(Entity, &Handle), With>, + config: Res, + mut views: Query<(&ExtractedView, &mut RenderPhase)>, +) { + let draw_function = draw_functions.read().get_id::().unwrap(); + let key = MeshPipelineKey::from_msaa_samples(msaa.samples()); + for (view, mut phase) in &mut views { + let key = key | MeshPipelineKey::from_hdr(view.hdr); + for (entity, mesh_handle) in &mesh_handles { + if let Some(mesh) = render_meshes.get(mesh_handle) { + let key = key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); + let pipeline = pipelines + .specialize( + &pipeline_cache, + &pipeline, + (!config.on_top, key), + &mesh.layout, + ) + .unwrap(); + phase.add(Opaque3d { + entity, + pipeline, + draw_function, + distance: 0., + }); + } + } + } +} diff --git a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs index 557450f9af39d..ad8c6c1ec3dd4 100644 --- a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs +++ b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use bevy_app::{App, 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 Default for ValidParentCheckPlugin { impl Plugin for ValidParentCheckPlugin { fn build(&self, app: &mut App) { - app.init_resource::>().add_system( + app.init_resource::>().add_systems( + Last, check_hierarchy_component_has_valid_parent:: - .run_if(resource_equals(ReportHierarchyIssue::::new(true))) - .in_base_set(CoreSet::Last), + .run_if(resource_equals(ReportHierarchyIssue::::new(true))), ); } } diff --git a/crates/bevy_input/src/common_conditions.rs b/crates/bevy_input/src/common_conditions.rs index 265b1c0c4dc50..8b85b8a858f44 100644 --- a/crates/bevy_input/src/common_conditions.rs +++ b/crates/bevy_input/src/common_conditions.rs @@ -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::() -/// .add_system(pause_menu.run_if(|paused: Res| paused.0)) +/// .add_systems(Update, pause_menu.run_if(|paused: Res| paused.0)) /// .run(); /// } /// @@ -48,7 +48,7 @@ use std::hash::Hash; /// } /// /// ``` -pub fn input_toggle_active(default: bool, input: T) -> impl FnMut(Res>) -> bool +pub fn input_toggle_active(default: bool, input: T) -> impl FnMut(Res>) -> bool + Clone where T: Copy + Eq + Hash + Send + Sync + 'static, { @@ -60,7 +60,7 @@ where } /// Run condition that is active if [`Input::pressed`] is true for the given input. -pub fn input_pressed(input: T) -> impl FnMut(Res>) -> bool +pub fn input_pressed(input: T) -> impl FnMut(Res>) -> bool + Clone where T: Copy + Eq + Hash + Send + Sync + 'static, { @@ -75,13 +75,13 @@ 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(); /// } /// /// # fn jump() {} /// ``` -pub fn input_just_pressed(input: T) -> impl FnMut(Res>) -> bool +pub fn input_just_pressed(input: T) -> impl FnMut(Res>) -> bool + Clone where T: Copy + Eq + Hash + Send + Sync + 'static, { @@ -89,9 +89,29 @@ where } /// Run condition that is active if [`Input::just_released`] is true for the given input. -pub fn input_just_released(input: T) -> impl FnMut(Res>) -> bool +pub fn input_just_released(input: T) -> impl FnMut(Res>) -> bool + Clone where T: Copy + Eq + Hash + Send + Sync + 'static, { move |inputs: Res>| inputs.just_released(input) } + +#[cfg(test)] +mod tests { + use super::*; + use bevy::prelude::{IntoSystemConfigs, KeyCode, Schedule}; + + fn test_system() {} + + // Ensure distributive_run_if compiles with the common conditions. + #[test] + fn distributive_run_if_compiles() { + Schedule::default().add_systems( + (test_system, test_system) + .distributive_run_if(input_toggle_active(false, KeyCode::Escape)) + .distributive_run_if(input_pressed(KeyCode::Escape)) + .distributive_run_if(input_just_pressed(KeyCode::Escape)) + .distributive_run_if(input_just_released(KeyCode::Escape)), + ); + } +} diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index 7fc988171cf56..9da05a2e25caf 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -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::() .init_resource::>() .init_resource::>() - .add_system(keyboard_input_system.in_set(InputSystem)) + .add_systems(PreUpdate, keyboard_input_system.in_set(InputSystem)) // mouse .add_event::() .add_event::() .add_event::() .init_resource::>() - .add_system(mouse_button_input_system.in_set(InputSystem)) + .add_systems(PreUpdate, mouse_button_input_system.in_set(InputSystem)) // gamepad .add_event::() .add_event::() @@ -76,6 +76,7 @@ impl Plugin for InputPlugin { .init_resource::>() .init_resource::>() .add_systems( + PreUpdate, ( gamepad_event_system, gamepad_connection_system.after(gamepad_event_system), @@ -91,7 +92,7 @@ impl Plugin for InputPlugin { // touch .add_event::() .init_resource::() - .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::(); diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 1eec1d8c5eac0..5af93188b37b3 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -71,11 +71,14 @@ subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"] webgl = ["bevy_core_pipeline?/webgl", "bevy_pbr?/webgl", "bevy_render?/webgl"] # enable systems that allow for automated testing on CI -bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_render/ci_limits"] +bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_render?/ci_limits"] # Enable animation support, and glTF animation loading animation = ["bevy_animation", "bevy_gltf?/bevy_animation"] +bevy_sprite = ["dep:bevy_sprite", "bevy_gizmos?/bevy_sprite"] +bevy_pbr = ["dep:bevy_pbr", "bevy_gizmos?/bevy_pbr"] + # Used to disable code that is unsupported when Bevy is dynamically linked dynamic_linking = ["bevy_diagnostic/dynamic_linking"] @@ -122,3 +125,4 @@ bevy_text = { path = "../bevy_text", optional = true, version = "0.11.0-dev" } bevy_ui = { path = "../bevy_ui", optional = true, version = "0.11.0-dev" } bevy_winit = { path = "../bevy_winit", optional = true, version = "0.11.0-dev" } bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.11.0-dev" } +bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.11.0-dev", default-features = false } diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index 2c3e637de8e0f..fe3be50bf9536 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -135,6 +135,11 @@ impl PluginGroup for DefaultPlugins { group = group.add(bevy_animation::AnimationPlugin::default()); } + #[cfg(feature = "bevy_gizmos")] + { + group = group.add(bevy_gizmos::GizmoPlugin); + } + group } } diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index b2211a5e3f751..4e1c6bcb179d9 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -172,6 +172,12 @@ pub mod winit { pub use bevy_winit::*; } +#[cfg(feature = "bevy_gizmos")] +pub mod gizmos { + //! Immediate mode debug drawing. + pub use bevy_gizmos::*; +} + #[cfg(feature = "bevy_dynamic_plugin")] pub mod dynamic_plugin { //! Dynamic linking of plugins diff --git a/crates/bevy_internal/src/prelude.rs b/crates/bevy_internal/src/prelude.rs index cc3614544d0bb..f9243382a11ef 100644 --- a/crates/bevy_internal/src/prelude.rs +++ b/crates/bevy_internal/src/prelude.rs @@ -51,6 +51,10 @@ pub use crate::ui::prelude::*; #[cfg(feature = "bevy_dynamic_plugin")] pub use crate::dynamic_plugin::*; +#[doc(hidden)] +#[cfg(feature = "bevy_gizmos")] +pub use crate::gizmos::prelude::*; + #[doc(hidden)] #[cfg(feature = "bevy_gilrs")] pub use crate::gilrs::*; diff --git a/crates/bevy_macro_utils/Cargo.toml b/crates/bevy_macro_utils/Cargo.toml index 8b9149864aa67..76cb1d690f295 100644 --- a/crates/bevy_macro_utils/Cargo.toml +++ b/crates/bevy_macro_utils/Cargo.toml @@ -12,3 +12,4 @@ keywords = ["bevy"] toml_edit = "0.19" syn = "1.0" quote = "1.0" +rustc-hash = "1.0" diff --git a/crates/bevy_macro_utils/src/lib.rs b/crates/bevy_macro_utils/src/lib.rs index 3fd275ceb9f48..3a72994510777 100644 --- a/crates/bevy_macro_utils/src/lib.rs +++ b/crates/bevy_macro_utils/src/lib.rs @@ -8,10 +8,11 @@ pub use attrs::*; pub use shape::*; pub use symbol::*; -use proc_macro::TokenStream; +use proc_macro::{TokenStream, TokenTree}; use quote::{quote, quote_spanned}; +use rustc_hash::FxHashSet; use std::{env, path::PathBuf}; -use syn::spanned::Spanned; +use syn::{spanned::Spanned, Ident}; use toml_edit::{Document, Item}; pub struct BevyManifest { @@ -108,6 +109,48 @@ impl BevyManifest { } } +/// Finds an identifier that will not conflict with the specified set of tokens. +/// If the identifier is present in `haystack`, extra characters will be added +/// to it until it no longer conflicts with anything. +/// +/// Note that the returned identifier can still conflict in niche cases, +/// such as if an identifier in `haystack` is hidden behind an un-expanded macro. +pub fn ensure_no_collision(value: Ident, haystack: TokenStream) -> Ident { + // Collect all the identifiers in `haystack` into a set. + let idents = { + // List of token streams that will be visited in future loop iterations. + let mut unvisited = vec![haystack]; + // Identifiers we have found while searching tokens. + let mut found = FxHashSet::default(); + while let Some(tokens) = unvisited.pop() { + for t in tokens { + match t { + // Collect any identifiers we encounter. + TokenTree::Ident(ident) => { + found.insert(ident.to_string()); + } + // Queue up nested token streams to be visited in a future loop iteration. + TokenTree::Group(g) => unvisited.push(g.stream()), + TokenTree::Punct(_) | TokenTree::Literal(_) => {} + } + } + } + + found + }; + + let span = value.span(); + + // If there's a collision, add more characters to the identifier + // until it doesn't collide with anything anymore. + let mut value = value.to_string(); + while idents.contains(&value) { + value.push('X'); + } + + Ident::new(&value, span) +} + /// Derive a label trait /// /// # Args diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index a55458d802dac..b3d7741db1d52 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -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; @@ -151,6 +151,7 @@ impl Plugin for PbrPlugin { app.register_asset_reflect::() .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() @@ -175,59 +176,58 @@ impl Plugin for PbrPlugin { .init_resource::() .add_plugin(ExtractResourcePlugin::::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::>() @@ -247,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::.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::.in_set(RenderSet::PhaseSort), - )) .init_resource::() .init_resource::() .init_resource::(); @@ -286,11 +294,5 @@ impl Plugin for PbrPlugin { draw_3d_graph::node::SHADOW_PASS, bevy_core_pipeline::core_3d::graph::node::MAIN_PASS, ); - draw_3d_graph.add_slot_edge( - draw_3d_graph.input_node().id, - bevy_core_pipeline::core_3d::graph::input::VIEW_ENTITY, - draw_3d_graph::node::SHADOW_PASS, - ShadowPassNode::IN_VIEW, - ); } } diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 0072474083164..be28f2e13a5e9 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -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::>() .init_resource::>() .init_resource::>>() - .add_systems(( - extract_materials::.in_schedule(ExtractSchedule), - prepare_materials:: - .in_set(RenderSet::Prepare) - .after(PrepareAssetSet::PreAssetPrepare), - render::queue_shadows::.in_set(RenderLightSystems::QueueShadows), - queue_material_meshes::.in_set(RenderSet::Queue), - )); + .add_systems(ExtractSchedule, extract_materials::) + .add_systems( + Render, + ( + prepare_materials:: + .in_set(RenderSet::Prepare) + .after(PrepareAssetSet::PreAssetPrepare), + render::queue_shadows::.in_set(RenderLightSystems::QueueShadows), + queue_material_meshes::.in_set(RenderSet::Queue), + ), + ); } // PrepassPipelinePlugin is required for shadow mapping and the optional PrepassPlugin diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index b890de737de49..b2a50c5f37576 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -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, @@ -17,6 +17,7 @@ use bevy_ecs::{ use bevy_reflect::TypeUuid; use bevy_render::{ camera::ExtractedCamera, + globals::{GlobalsBuffer, GlobalsUniform}, mesh::MeshVertexBufferLayout, prelude::{Camera, Mesh}, render_asset::RenderAssets, @@ -38,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}; @@ -101,7 +102,10 @@ where }; render_app - .add_system(queue_prepass_view_bind_group::.in_set(RenderSet::Queue)) + .add_systems( + Render, + queue_prepass_view_bind_group::.in_set(RenderSet::Queue), + ) .init_resource::>() .init_resource::() .init_resource::>>(); @@ -129,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::.in_set(RenderSet::Queue), - sort_phase_system::.in_set(RenderSet::PhaseSort), - sort_phase_system::.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::.in_set(RenderSet::Queue), + sort_phase_system::.in_set(RenderSet::PhaseSort), + sort_phase_system::.in_set(RenderSet::PhaseSort), + ), + ) .init_resource::>() .init_resource::>() .add_render_command::>() @@ -167,7 +174,7 @@ impl FromWorld for PrepassPipeline { // View BindGroupLayoutEntry { binding: 0, - visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + visibility: ShaderStages::VERTEX_FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, @@ -175,6 +182,17 @@ impl FromWorld for PrepassPipeline { }, count: None, }, + // Globals + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::VERTEX_FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: Some(GlobalsUniform::min_size()), + }, + count: None, + }, ], label: Some("prepass_view_layout"), }); @@ -573,19 +591,26 @@ pub fn queue_prepass_view_bind_group( render_device: Res, prepass_pipeline: Res>, view_uniforms: Res, + globals_buffer: Res, mut prepass_view_bind_group: ResMut, ) { - if let Some(view_binding) = view_uniforms.uniforms.binding() { - prepass_view_bind_group.bind_group = - Some(render_device.create_bind_group(&BindGroupDescriptor { - entries: &[BindGroupEntry { + let Some(view_binding) = view_uniforms.uniforms.binding() else { return }; + let Some(globals_binding) = globals_buffer.buffer.binding() else { return }; + prepass_view_bind_group.bind_group = + Some(render_device.create_bind_group(&BindGroupDescriptor { + entries: &[ + BindGroupEntry { binding: 0, resource: view_binding, - }], - label: Some("prepass_view_bind_group"), - layout: &prepass_pipeline.view_layout, - })); - } + }, + BindGroupEntry { + binding: 1, + resource: globals_binding, + }, + ], + label: Some("prepass_view_bind_group"), + layout: &prepass_pipeline.view_layout, + })); } #[allow(clippy::too_many_arguments)] diff --git a/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl b/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl index cd338af0ed7da..528f2e596d139 100644 --- a/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass_bindings.wgsl @@ -6,6 +6,9 @@ @group(0) @binding(0) var view: View; +@group(0) @binding(1) +var globals: Globals; + // Material bindings will be in @group(1) @group(2) @binding(0) @@ -16,3 +19,4 @@ var mesh: Mesh; var joint_matrices: SkinnedMesh; #import bevy_pbr::skinning #endif + diff --git a/crates/bevy_pbr/src/render/fog.rs b/crates/bevy_pbr/src/render/fog.rs index 2c658bb7a16e4..efa9a4c15f0fe 100644 --- a/crates/bevy_pbr/src/render/fog.rs +++ b/crates/bevy_pbr/src/render/fog.rs @@ -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::() - .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), + ); } } } diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index cdf1e7ab1548e..1df01bfe05d8d 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -15,7 +15,7 @@ use bevy_render::{ color::Color, mesh::Mesh, render_asset::RenderAssets, - render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, + render_graph::{Node, NodeRunError, RenderGraphContext}, render_phase::{ CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, RenderPhase, }, @@ -472,37 +472,43 @@ pub(crate) struct CubeMapFace { pub(crate) up: Vec3, } -// see https://www.khronos.org/opengl/wiki/Cubemap_Texture +// Cubemap faces are [+X, -X, +Y, -Y, +Z, -Z], per https://www.w3.org/TR/webgpu/#texture-view-creation +// Note: Cubemap coordinates are left-handed y-up, unlike the rest of Bevy. +// See https://registry.khronos.org/vulkan/specs/1.2/html/chap16.html#_cube_map_face_selection +// +// For each cubemap face, we take care to specify the appropriate target/up axis such that the rendered +// texture using Bevy's right-handed y-up coordinate space matches the expected cubemap face in +// left-handed y-up cubemap coordinates. pub(crate) const CUBE_MAP_FACES: [CubeMapFace; 6] = [ - // 0 GL_TEXTURE_CUBE_MAP_POSITIVE_X + // +X CubeMapFace { - target: Vec3::NEG_X, - up: Vec3::NEG_Y, + target: Vec3::X, + up: Vec3::Y, }, - // 1 GL_TEXTURE_CUBE_MAP_NEGATIVE_X + // -X CubeMapFace { - target: Vec3::X, - up: Vec3::NEG_Y, + target: Vec3::NEG_X, + up: Vec3::Y, }, - // 2 GL_TEXTURE_CUBE_MAP_POSITIVE_Y + // +Y CubeMapFace { - target: Vec3::NEG_Y, + target: Vec3::Y, up: Vec3::Z, }, - // 3 GL_TEXTURE_CUBE_MAP_NEGATIVE_Y + // -Y CubeMapFace { - target: Vec3::Y, + target: Vec3::NEG_Y, up: Vec3::NEG_Z, }, - // 4 GL_TEXTURE_CUBE_MAP_POSITIVE_Z + // +Z (with left-handed conventions, pointing forwards) CubeMapFace { target: Vec3::NEG_Z, - up: Vec3::NEG_Y, + up: Vec3::Y, }, - // 5 GL_TEXTURE_CUBE_MAP_NEGATIVE_Z + // -Z (with left-handed conventions, pointing backwards) CubeMapFace { target: Vec3::Z, - up: Vec3::NEG_Y, + up: Vec3::Y, }, ]; @@ -1691,8 +1697,6 @@ pub struct ShadowPassNode { } impl ShadowPassNode { - pub const IN_VIEW: &'static str = "view"; - pub fn new(world: &mut World) -> Self { Self { main_view_query: QueryState::new(world), @@ -1702,10 +1706,6 @@ impl ShadowPassNode { } impl Node for ShadowPassNode { - fn input(&self) -> Vec { - vec![SlotInfo::new(ShadowPassNode::IN_VIEW, SlotType::Entity)] - } - fn update(&mut self, world: &mut World) { self.main_view_query.update_archetypes(world); self.view_light_query.update_archetypes(world); @@ -1717,7 +1717,7 @@ impl Node for ShadowPassNode { render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { - let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + let view_entity = graph.view_entity(); if let Ok(view_lights) = self.main_view_query.get_manual(world, view_entity) { for view_light_entity in view_lights.lights.iter().copied() { let (view_light, shadow_phase) = self diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 1faf66bc4c19f..a76ff0f470712 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -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::() .init_resource::() - .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), + ), + ); } } } diff --git a/crates/bevy_pbr/src/render/shadows.wgsl b/crates/bevy_pbr/src/render/shadows.wgsl index f74733c90051e..8da216803b934 100644 --- a/crates/bevy_pbr/src/render/shadows.wgsl +++ b/crates/bevy_pbr/src/render/shadows.wgsl @@ -1,5 +1,7 @@ #define_import_path bevy_pbr::shadows +const flip_z: vec3 = vec3(1.0, 1.0, -1.0); + fn fetch_point_shadow(light_id: u32, frag_position: vec4, surface_normal: vec3) -> f32 { let light = &point_lights.data[light_id]; @@ -17,7 +19,7 @@ fn fetch_point_shadow(light_id: u32, frag_position: vec4, surface_normal: v let offset_position = frag_position.xyz + normal_offset + depth_offset; // similar largest-absolute-axis trick as above, but now with the offset fragment position - let frag_ls = (*light).position_radius.xyz - offset_position.xyz; + let frag_ls = offset_position.xyz - (*light).position_radius.xyz ; let abs_position_ls = abs(frag_ls); let major_axis_magnitude = max(abs_position_ls.x, max(abs_position_ls.y, abs_position_ls.z)); @@ -28,16 +30,17 @@ fn fetch_point_shadow(light_id: u32, frag_position: vec4, surface_normal: v let zw = -major_axis_magnitude * (*light).light_custom_data.xy + (*light).light_custom_data.zw; let depth = zw.x / zw.y; - // do the lookup, using HW PCF and comparison + // Do the lookup, using HW PCF and comparison. Cubemaps assume a left-handed coordinate space, + // so we have to flip the z-axis when sampling. // NOTE: Due to the non-uniform control flow above, we must use the Level variant of // textureSampleCompare to avoid undefined behaviour due to some of the fragments in // a quad (2x2 fragments) being processed not being sampled, and this messing with // mip-mapping functionality. The shadow maps have no mipmaps so Level just samples // from LOD 0. #ifdef NO_ARRAY_TEXTURES_SUPPORT - return textureSampleCompare(point_shadow_textures, point_shadow_textures_sampler, frag_ls, depth); + return textureSampleCompare(point_shadow_textures, point_shadow_textures_sampler, frag_ls * flip_z, depth); #else - return textureSampleCompareLevel(point_shadow_textures, point_shadow_textures_sampler, frag_ls, i32(light_id), depth); + return textureSampleCompareLevel(point_shadow_textures, point_shadow_textures_sampler, frag_ls * flip_z, i32(light_id), depth); #endif } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 34d93c91853c8..7e885f9231558 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -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::() .init_resource::() .init_resource::>() - .add_system(queue_wireframes.in_set(RenderSet::Queue)); + .add_systems(Render, queue_wireframes.in_set(RenderSet::Queue)); } } } diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 27687fc863a7f..8b0e44ab0b034 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -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( mut window_resized_events: EventReader, mut window_created_events: EventReader, diff --git a/crates/bevy_render/src/camera/camera_driver_node.rs b/crates/bevy_render/src/camera/camera_driver_node.rs index dcee0cba45045..84ff1896fcba5 100644 --- a/crates/bevy_render/src/camera/camera_driver_node.rs +++ b/crates/bevy_render/src/camera/camera_driver_node.rs @@ -1,6 +1,6 @@ use crate::{ camera::{ExtractedCamera, NormalizedRenderTarget, SortedCameras}, - render_graph::{Node, NodeRunError, RenderGraphContext, SlotValue}, + render_graph::{Node, NodeRunError, RenderGraphContext}, renderer::RenderContext, view::ExtractedWindows, }; @@ -39,7 +39,8 @@ impl Node for CameraDriverNode { } graph.run_sub_graph( camera.render_graph.clone(), - vec![SlotValue::Entity(sorted_camera.entity)], + vec![], + Some(sorted_camera.entity), )?; } } diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index 91ea69d9417c7..44d72cb03e886 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -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::() - .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::(); render_graph.add_node(crate::main_graph::node::CAMERA_DRIVER, camera_driver_node); diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index fd82cd72da44e..ad3731be456ed 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use bevy_app::{App, 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 Plugin for CameraProjectionPlugin { fn build(&self, app: &mut App) { app.register_type::() - .edit_schedule(CoreSchedule::Startup, |schedule| { - schedule.configure_set(CameraUpdateSystem.in_base_set(StartupSet::PostStartup)); - }) - .configure_set(CameraUpdateSystem.in_base_set(CoreSet::PostUpdate)) - .add_systems(( + .add_systems( + PostStartup, crate::camera::camera_system:: - .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( + PostUpdate, crate::camera::camera_system:: .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), - )); + ); } } diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index 2b70a897a16fc..606c8ccbe09ac 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -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 Plugin for UniformComponentP if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .insert_resource(ComponentUniforms::::default()) - .add_system(prepare_uniform_components::.in_set(RenderSet::Prepare)); + .add_systems( + Render, + prepare_uniform_components::.in_set(RenderSet::Prepare), + ); } } } @@ -180,9 +183,9 @@ impl Plugin for ExtractComponentPlugin { fn build(&self, app: &mut App) { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { if self.only_extract_visible { - render_app.add_system(extract_visible_components::.in_schedule(ExtractSchedule)); + render_app.add_systems(ExtractSchedule, extract_visible_components::); } else { - render_app.add_system(extract_components::.in_schedule(ExtractSchedule)); + render_app.add_systems(ExtractSchedule, extract_components::); } } } diff --git a/crates/bevy_render/src/extract_resource.rs b/crates/bevy_render/src/extract_resource.rs index a1e8b122f0857..ea8f7e81fd52c 100644 --- a/crates/bevy_render/src/extract_resource.rs +++ b/crates/bevy_render/src/extract_resource.rs @@ -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 Default for ExtractResourcePlugin { impl Plugin for ExtractResourcePlugin { fn build(&self, app: &mut App) { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_system(extract_resource::.in_schedule(ExtractSchedule)); + render_app.add_systems(ExtractSchedule, extract_resource::); } } } diff --git a/crates/bevy_render/src/globals.rs b/crates/bevy_render/src/globals.rs index 6b9fbdbc7f3d2..ef585000b84ce 100644 --- a/crates/bevy_render/src/globals.rs +++ b/crates/bevy_render/src/globals.rs @@ -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::() .init_resource:: { fn build(&self, app: &mut App) { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app + .init_resource::>() + .init_resource::>() + .init_resource::>() + .add_systems(ExtractSchedule, extract_render_asset::) .configure_sets( + Render, ( PrepareAssetSet::PreAssetPrepare, PrepareAssetSet::AssetPrepare, @@ -89,13 +94,10 @@ impl Plugin for RenderAssetPlugin { .chain() .in_set(RenderSet::Prepare), ) - .init_resource::>() - .init_resource::>() - .init_resource::>() - .add_systems(( - extract_render_asset::.in_schedule(ExtractSchedule), + .add_systems( + Render, prepare_assets::.in_set(self.prepare_asset_set.clone()), - )); + ); } } } diff --git a/crates/bevy_render/src/render_graph/context.rs b/crates/bevy_render/src/render_graph/context.rs index 6a4ee01afa0ee..da983fdc7fbae 100644 --- a/crates/bevy_render/src/render_graph/context.rs +++ b/crates/bevy_render/src/render_graph/context.rs @@ -11,6 +11,7 @@ use thiserror::Error; pub struct RunSubGraph { pub name: Cow<'static, str>, pub inputs: Vec, + pub view_entity: Option, } /// The context with all graph information required to run a [`Node`](super::Node). @@ -27,6 +28,11 @@ pub struct RenderGraphContext<'a> { inputs: &'a [SlotValue], outputs: &'a mut [Option], run_sub_graphs: Vec, + /// The view_entity associated with the render graph being executed + /// This is optional because you aren't required to have a view_entity for a node. + /// For example, compute shader nodes don't have one. + /// It should always be set when the RenderGraph is running on a View. + view_entity: Option, } impl<'a> RenderGraphContext<'a> { @@ -43,6 +49,7 @@ impl<'a> RenderGraphContext<'a> { inputs, outputs, run_sub_graphs: Vec::new(), + view_entity: None, } } @@ -158,11 +165,24 @@ impl<'a> RenderGraphContext<'a> { Ok(()) } + pub fn view_entity(&self) -> Entity { + self.view_entity.unwrap() + } + + pub fn get_view_entity(&self) -> Option { + self.view_entity + } + + pub fn set_view_entity(&mut self, view_entity: Entity) { + self.view_entity = Some(view_entity); + } + /// Queues up a sub graph for execution after the node has finished running. pub fn run_sub_graph( &mut self, name: impl Into>, inputs: Vec, + view_entity: Option, ) -> Result<(), RunSubGraphError> { let name = name.into(); let sub_graph = self @@ -193,7 +213,11 @@ impl<'a> RenderGraphContext<'a> { return Err(RunSubGraphError::SubGraphHasNoInputs(name)); } - self.run_sub_graphs.push(RunSubGraph { name, inputs }); + self.run_sub_graphs.push(RunSubGraph { + name, + inputs, + view_entity, + }); Ok(()) } diff --git a/crates/bevy_render/src/render_graph/node.rs b/crates/bevy_render/src/render_graph/node.rs index 11db4aca83ac8..fea7eee2d31bf 100644 --- a/crates/bevy_render/src/render_graph/node.rs +++ b/crates/bevy_render/src/render_graph/node.rs @@ -2,7 +2,7 @@ use crate::{ define_atomic_id, render_graph::{ Edge, InputSlotError, OutputSlotError, RenderGraphContext, RenderGraphError, - RunSubGraphError, SlotInfo, SlotInfos, SlotType, SlotValue, + RunSubGraphError, SlotInfo, SlotInfos, }, renderer::RenderContext, }; @@ -306,14 +306,13 @@ impl Node for EmptyNode { } } -/// A [`RenderGraph`](super::RenderGraph) [`Node`] that takes a view entity as input and runs the configured graph name once. +/// A [`RenderGraph`](super::RenderGraph) [`Node`] that runs the configured graph name once. /// This makes it easier to insert sub-graph runs into a graph. pub struct RunGraphOnViewNode { graph_name: Cow<'static, str>, } impl RunGraphOnViewNode { - pub const IN_VIEW: &'static str = "view"; pub fn new>>(graph_name: T) -> Self { Self { graph_name: graph_name.into(), @@ -322,20 +321,13 @@ impl RunGraphOnViewNode { } impl Node for RunGraphOnViewNode { - fn input(&self) -> Vec { - vec![SlotInfo::new(Self::IN_VIEW, SlotType::Entity)] - } fn run( &self, graph: &mut RenderGraphContext, _render_context: &mut RenderContext, _world: &World, ) -> Result<(), NodeRunError> { - let view_entity = graph.get_input_entity(Self::IN_VIEW)?; - graph.run_sub_graph( - self.graph_name.clone(), - vec![SlotValue::Entity(view_entity)], - )?; + graph.run_sub_graph(self.graph_name.clone(), vec![], Some(graph.view_entity()))?; Ok(()) } } diff --git a/crates/bevy_render/src/render_resource/shader.rs b/crates/bevy_render/src/render_resource/shader.rs index c45c687e90eee..a0b874c092f18 100644 --- a/crates/bevy_render/src/render_resource/shader.rs +++ b/crates/bevy_render/src/render_resource/shader.rs @@ -296,8 +296,6 @@ pub enum ProcessShaderError { "Not enough '# endif' lines. Each if statement should be followed by an endif statement." )] NotEnoughEndIfs, - #[error("This Shader's format does not support processing shader defs.")] - ShaderFormatDoesNotSupportShaderDefs, #[error("This Shader's format does not support imports.")] ShaderFormatDoesNotSupportImports, #[error("Unresolved import: {0:?}.")] @@ -477,10 +475,7 @@ impl ShaderProcessor { Source::Wgsl(source) => source.deref(), Source::Glsl(source, _stage) => source.deref(), Source::SpirV(source) => { - if shader_defs_unique.is_empty() { - return Ok(ProcessedShader::SpirV(source.clone())); - } - return Err(ProcessShaderError::ShaderFormatDoesNotSupportShaderDefs); + return Ok(ProcessedShader::SpirV(source.clone())); } }; diff --git a/crates/bevy_render/src/renderer/graph_runner.rs b/crates/bevy_render/src/renderer/graph_runner.rs index e11c61f6ca064..478298dd426d3 100644 --- a/crates/bevy_render/src/renderer/graph_runner.rs +++ b/crates/bevy_render/src/renderer/graph_runner.rs @@ -1,4 +1,4 @@ -use bevy_ecs::world::World; +use bevy_ecs::{prelude::Entity, world::World}; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; use bevy_utils::HashMap; @@ -59,7 +59,7 @@ impl RenderGraphRunner { world: &World, ) -> Result<(), RenderGraphRunnerError> { let mut render_context = RenderContext::new(render_device); - Self::run_graph(graph, None, &mut render_context, world, &[])?; + Self::run_graph(graph, None, &mut render_context, world, &[], None)?; { #[cfg(feature = "trace")] let _span = info_span!("submit_graph_commands").entered(); @@ -74,6 +74,7 @@ impl RenderGraphRunner { render_context: &mut RenderContext, world: &World, inputs: &[SlotValue], + view_entity: Option, ) -> Result<(), RenderGraphRunnerError> { let mut node_outputs: HashMap> = HashMap::default(); #[cfg(feature = "trace")] @@ -175,6 +176,10 @@ impl RenderGraphRunner { smallvec![None; node_state.output_slots.len()]; { let mut context = RenderGraphContext::new(graph, node_state, &inputs, &mut outputs); + if let Some(view_entity) = view_entity { + context.set_view_entity(view_entity); + } + { #[cfg(feature = "trace")] let _span = info_span!("node", name = node_state.type_name).entered(); @@ -192,6 +197,7 @@ impl RenderGraphRunner { render_context, world, &run_sub_graph.inputs, + run_sub_graph.view_entity, )?; } } diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index f9b32a1eb754b..1f4f45a83bbbf 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -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::() .init_resource::() .init_resource::() - .add_system(update_texture_cache_system.in_set(RenderSet::Cleanup)); + .add_systems( + Render, + update_texture_cache_system.in_set(RenderSet::Cleanup), + ); } } } diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index fa8bf29603723..39bc2d14194d6 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -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::() - .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::), - )); + .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::), + ), + ); } } } diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 3163f7a831b37..25f0fc02fa087 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -2,7 +2,7 @@ mod render_layers; pub use render_layers::*; -use bevy_app::{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:: - .in_set(UpdateOrthographicFrusta) - .after(camera_system::) - .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::) - .ambiguous_with(update_frusta::), - update_frusta:: - .in_set(UpdatePerspectiveFrusta) - .after(camera_system::) - .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::), - update_frusta:: - .in_set(UpdateProjectionFrusta) - .after(camera_system::) - .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:: + .in_set(UpdateOrthographicFrusta) + .after(camera_system::) + .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::) + .ambiguous_with(update_frusta::), + update_frusta:: + .in_set(UpdatePerspectiveFrusta) + .after(camera_system::) + .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::), + update_frusta:: + .in_set(UpdateProjectionFrusta) + .after(camera_system::) + .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 diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index ca9fac5aabbba..8118e4fd06d8f 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -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::() .init_resource::() .init_non_send_resource::() - .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)); } } } diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index cb5bd1a387fce..adac64c425c83 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -10,10 +10,13 @@ use bevy_reflect::{Reflect, TypeRegistryArc, TypeUuid}; #[cfg(feature = "serialize")] use crate::serde::SceneSerializer; +use bevy_ecs::reflect::ReflectResource; #[cfg(feature = "serialize")] use serde::Serialize; -/// A collection of serializable dynamic entities, each with its own run-time defined set of components. +/// A collection of serializable resources and dynamic entities. +/// +/// Each dynamic entity in the collection contains its own run-time defined set of components. /// To spawn a dynamic scene, you can use either: /// * [`SceneSpawner::spawn_dynamic`](crate::SceneSpawner::spawn_dynamic) /// * adding the [`DynamicSceneBundle`](crate::DynamicSceneBundle) to an entity @@ -23,6 +26,7 @@ use serde::Serialize; #[derive(Default, TypeUuid)] #[uuid = "749479b1-fb8c-4ff8-a775-623aa76014f5"] pub struct DynamicScene { + pub resources: Vec>, pub entities: Vec, } @@ -47,15 +51,16 @@ impl DynamicScene { DynamicSceneBuilder::from_world_with_type_registry(world, type_registry.clone()); builder.extract_entities(world.iter_entities().map(|entity| entity.id())); + builder.extract_resources(); builder.build() } - /// Write the dynamic entities and their corresponding components to the given world. + /// Write the resources, the dynamic entities, and their corresponding components to the given world. /// /// This method will return a [`SceneSpawnError`] if a type either is not registered /// in the provided [`AppTypeRegistry`] resource, or doesn't reflect the - /// [`Component`](bevy_ecs::component::Component) trait. + /// [`Component`](bevy_ecs::component::Component) or [`Resource`](bevy_ecs::prelude::Resource) trait. pub fn write_to_world_with( &self, world: &mut World, @@ -64,6 +69,23 @@ impl DynamicScene { ) -> Result<(), SceneSpawnError> { let type_registry = type_registry.read(); + for resource in &self.resources { + let registration = type_registry + .get_with_name(resource.type_name()) + .ok_or_else(|| SceneSpawnError::UnregisteredType { + type_name: resource.type_name().to_string(), + })?; + let reflect_resource = registration.data::().ok_or_else(|| { + SceneSpawnError::UnregisteredResource { + type_name: resource.type_name().to_string(), + } + })?; + + // If the world already contains an instance of the given resource + // just apply the (possibly) new value, otherwise insert the resource + reflect_resource.apply_or_insert(world, &**resource); + } + for scene_entity in &self.entities { // Fetch the entity with the given entity id from the `entity_map` // or spawn a new entity with a transiently unique id if there is @@ -105,7 +127,7 @@ impl DynamicScene { Ok(()) } - /// Write the dynamic entities and their corresponding components to the given world. + /// Write the resources, the dynamic entities, and their corresponding components to the given world. /// /// This method will return a [`SceneSpawnError`] if a type either is not registered /// in the world's [`AppTypeRegistry`] resource, or doesn't reflect the diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 1187f087b6809..0f25b1e484ba3 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -1,10 +1,16 @@ use crate::{DynamicEntity, DynamicScene}; use bevy_app::AppTypeRegistry; -use bevy_ecs::{prelude::Entity, reflect::ReflectComponent, world::World}; +use bevy_ecs::component::ComponentId; +use bevy_ecs::{ + prelude::Entity, + reflect::{ReflectComponent, ReflectResource}, + world::World, +}; +use bevy_reflect::Reflect; use bevy_utils::default; use std::collections::BTreeMap; -/// A [`DynamicScene`] builder, used to build a scene from a [`World`] by extracting some entities. +/// A [`DynamicScene`] builder, used to build a scene from a [`World`] by extracting some entities and resources. /// /// # Entity Order /// @@ -31,6 +37,7 @@ use std::collections::BTreeMap; /// let dynamic_scene = builder.build(); /// ``` pub struct DynamicSceneBuilder<'w> { + extracted_resources: BTreeMap>, extracted_scene: BTreeMap, type_registry: AppTypeRegistry, original_world: &'w World, @@ -41,6 +48,7 @@ impl<'w> DynamicSceneBuilder<'w> { /// All components registered in that world's [`AppTypeRegistry`] resource will be extracted. pub fn from_world(world: &'w World) -> Self { Self { + extracted_resources: default(), extracted_scene: default(), type_registry: world.resource::().clone(), original_world: world, @@ -51,6 +59,7 @@ impl<'w> DynamicSceneBuilder<'w> { /// Only components registered in the given [`AppTypeRegistry`] will be extracted. pub fn from_world_with_type_registry(world: &'w World, type_registry: AppTypeRegistry) -> Self { Self { + extracted_resources: default(), extracted_scene: default(), type_registry, original_world: world, @@ -63,6 +72,7 @@ impl<'w> DynamicSceneBuilder<'w> { /// [`Self::remove_empty_entities`] before building the scene. pub fn build(self) -> DynamicScene { DynamicScene { + resources: self.extracted_resources.into_values().collect(), entities: self.extracted_scene.into_values().collect(), } } @@ -126,17 +136,20 @@ impl<'w> DynamicSceneBuilder<'w> { let entity = self.original_world.entity(entity); for component_id in entity.archetype().components() { - let reflect_component = self - .original_world - .components() - .get_info(component_id) - .and_then(|info| type_registry.get(info.type_id().unwrap())) - .and_then(|registration| registration.data::()) - .and_then(|reflect_component| reflect_component.reflect(entity)); - - if let Some(reflect_component) = reflect_component { - entry.components.push(reflect_component.clone_value()); - } + let mut extract_and_push = || { + let type_id = self + .original_world + .components() + .get_info(component_id)? + .type_id()?; + let component = type_registry + .get(type_id)? + .data::()? + .reflect(entity)?; + entry.components.push(component.clone_value()); + Some(()) + }; + extract_and_push(); } self.extracted_scene.insert(index, entry); } @@ -144,13 +157,59 @@ impl<'w> DynamicSceneBuilder<'w> { drop(type_registry); self } + + /// Extract resources from the builder's [`World`]. + /// + /// Only resources registered in the builder's [`AppTypeRegistry`] will be extracted. + /// Re-extracting a resource that was already extracted will have no effect. + /// ``` + /// # use bevy_scene::DynamicSceneBuilder; + /// # use bevy_app::AppTypeRegistry; + /// # use bevy_ecs::prelude::{ReflectResource, Resource, World}; + /// # use bevy_reflect::Reflect; + /// #[derive(Resource, Default, Reflect)] + /// #[reflect(Resource)] + /// struct MyResource; + /// + /// # let mut world = World::default(); + /// # world.init_resource::(); + /// world.insert_resource(MyResource); + /// + /// let mut builder = DynamicSceneBuilder::from_world(&world); + /// builder.extract_resources(); + /// let scene = builder.build(); + /// ``` + pub fn extract_resources(&mut self) -> &mut Self { + let type_registry = self.type_registry.read(); + for (component_id, _) in self.original_world.storages().resources.iter() { + let mut extract_and_push = || { + let type_id = self + .original_world + .components() + .get_info(component_id)? + .type_id()?; + let resource = type_registry + .get(type_id)? + .data::()? + .reflect(self.original_world)?; + self.extracted_resources + .insert(component_id, resource.clone_value()); + Some(()) + }; + extract_and_push(); + } + + drop(type_registry); + self + } } #[cfg(test)] mod tests { use bevy_app::AppTypeRegistry; use bevy_ecs::{ - component::Component, prelude::Entity, query::With, reflect::ReflectComponent, world::World, + component::Component, prelude::Entity, prelude::Resource, query::With, + reflect::ReflectComponent, reflect::ReflectResource, world::World, }; use bevy_reflect::Reflect; @@ -160,10 +219,15 @@ mod tests { #[derive(Component, Reflect, Default, Eq, PartialEq, Debug)] #[reflect(Component)] struct ComponentA; + #[derive(Component, Reflect, Default, Eq, PartialEq, Debug)] #[reflect(Component)] struct ComponentB; + #[derive(Resource, Reflect, Default, Eq, PartialEq, Debug)] + #[reflect(Resource)] + struct ResourceA; + #[test] fn extract_one_entity() { let mut world = World::default(); @@ -303,4 +367,41 @@ mod tests { assert_eq!(scene.entities.len(), 1); assert_eq!(scene.entities[0].entity, entity_a.index()); } + + #[test] + fn extract_one_resource() { + let mut world = World::default(); + + let atr = AppTypeRegistry::default(); + atr.write().register::(); + world.insert_resource(atr); + + world.insert_resource(ResourceA); + + let mut builder = DynamicSceneBuilder::from_world(&world); + builder.extract_resources(); + let scene = builder.build(); + + assert_eq!(scene.resources.len(), 1); + assert!(scene.resources[0].represents::()); + } + + #[test] + fn extract_one_resource_twice() { + let mut world = World::default(); + + let atr = AppTypeRegistry::default(); + atr.write().register::(); + world.insert_resource(atr); + + world.insert_resource(ResourceA); + + let mut builder = DynamicSceneBuilder::from_world(&world); + builder.extract_resources(); + builder.extract_resources(); + let scene = builder.build(); + + assert_eq!(scene.resources.len(), 1); + assert!(scene.resources[0].represents::()); + } } diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 2d7ef33951b30..3488064f10fe7 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -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::() .init_asset_loader::() .init_resource::() - .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); } } diff --git a/crates/bevy_scene/src/scene.rs b/crates/bevy_scene/src/scene.rs index 4df757aa5838f..460f7cf8b2f3a 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -1,7 +1,7 @@ use bevy_app::AppTypeRegistry; use bevy_ecs::{ entity::EntityMap, - reflect::{ReflectComponent, ReflectMapEntities}, + reflect::{ReflectComponent, ReflectMapEntities, ReflectResource}, world::World, }; use bevy_reflect::TypeUuid; @@ -61,6 +61,33 @@ impl Scene { }; let type_registry = type_registry.read(); + + // Resources archetype + for (component_id, _) in self.world.storages().resources.iter() { + let component_info = self + .world + .components() + .get_info(component_id) + .expect("component_ids in archetypes should have ComponentInfo"); + + let type_id = component_info + .type_id() + .expect("reflected resources must have a type_id"); + + let registration = + type_registry + .get(type_id) + .ok_or_else(|| SceneSpawnError::UnregisteredType { + type_name: component_info.name().to_string(), + })?; + let reflect_resource = registration.data::().ok_or_else(|| { + SceneSpawnError::UnregisteredResource { + type_name: component_info.name().to_string(), + } + })?; + reflect_resource.copy(&self.world, world); + } + for archetype in self.world.archetypes().iter() { for scene_entity in archetype.entities() { let entity = *instance_info diff --git a/crates/bevy_scene/src/scene_loader.rs b/crates/bevy_scene/src/scene_loader.rs index 2dea805f92dba..ed5c82443cc0f 100644 --- a/crates/bevy_scene/src/scene_loader.rs +++ b/crates/bevy_scene/src/scene_loader.rs @@ -1,5 +1,5 @@ use crate::serde::SceneDeserializer; -use anyhow::Result; +use anyhow::{anyhow, Result}; use bevy_app::AppTypeRegistry; use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; use bevy_ecs::world::{FromWorld, World}; @@ -35,7 +35,17 @@ impl AssetLoader for SceneLoader { let scene_deserializer = SceneDeserializer { type_registry: &self.type_registry.read(), }; - let scene = scene_deserializer.deserialize(&mut deserializer)?; + let scene = scene_deserializer + .deserialize(&mut deserializer) + .map_err(|e| { + let span_error = deserializer.span_error(e); + anyhow!( + "{} at {}:{}", + span_error.code, + load_context.path().to_string_lossy(), + span_error.position, + ) + })?; load_context.set_default_asset(LoadedAsset::new(scene)); Ok(()) }) diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 7a0c8f851b33b..5a46aa14e256c 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -45,6 +45,8 @@ pub struct SceneSpawner { pub enum SceneSpawnError { #[error("scene contains the unregistered component `{type_name}`. consider adding `#[reflect(Component)]` to your type")] UnregisteredComponent { type_name: String }, + #[error("scene contains the unregistered resource `{type_name}`. consider adding `#[reflect(Resource)]` to your type")] + UnregisteredResource { type_name: String }, #[error("scene contains the unregistered type `{type_name}`. consider registering the type using `app.register_type::()`")] UnregisteredType { type_name: String }, #[error("scene does not exist")] diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index 51b8a5ebce0e7..debe7c54515f3 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -15,6 +15,7 @@ use serde::{ use std::fmt::Formatter; pub const SCENE_STRUCT: &str = "Scene"; +pub const SCENE_RESOURCES: &str = "resources"; pub const SCENE_ENTITIES: &str = "entities"; pub const ENTITY_STRUCT: &str = "Entity"; @@ -36,7 +37,14 @@ impl<'a> Serialize for SceneSerializer<'a> { where S: serde::Serializer, { - let mut state = serializer.serialize_struct(SCENE_STRUCT, 1)?; + let mut state = serializer.serialize_struct(SCENE_STRUCT, 2)?; + state.serialize_field( + SCENE_RESOURCES, + &SceneMapSerializer { + entries: &self.scene.resources, + registry: self.registry, + }, + )?; state.serialize_field( SCENE_ENTITIES, &EntitiesSerializer { @@ -85,8 +93,8 @@ impl<'a> Serialize for EntitySerializer<'a> { let mut state = serializer.serialize_struct(ENTITY_STRUCT, 1)?; state.serialize_field( ENTITY_FIELD_COMPONENTS, - &ComponentsSerializer { - components: &self.entity.components, + &SceneMapSerializer { + entries: &self.entity.components, registry: self.registry, }, )?; @@ -94,21 +102,21 @@ impl<'a> Serialize for EntitySerializer<'a> { } } -pub struct ComponentsSerializer<'a> { - pub components: &'a [Box], +pub struct SceneMapSerializer<'a> { + pub entries: &'a [Box], pub registry: &'a TypeRegistryArc, } -impl<'a> Serialize for ComponentsSerializer<'a> { +impl<'a> Serialize for SceneMapSerializer<'a> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - let mut state = serializer.serialize_map(Some(self.components.len()))?; - for component in self.components { + let mut state = serializer.serialize_map(Some(self.entries.len()))?; + for reflect in self.entries { state.serialize_entry( - component.type_name(), - &TypedReflectSerializer::new(&**component, &self.registry.read()), + reflect.type_name(), + &TypedReflectSerializer::new(&**reflect, &self.registry.read()), )?; } state.end() @@ -118,6 +126,7 @@ impl<'a> Serialize for ComponentsSerializer<'a> { #[derive(Deserialize)] #[serde(field_identifier, rename_all = "lowercase")] enum SceneField { + Resources, Entities, } @@ -140,7 +149,7 @@ impl<'a, 'de> DeserializeSeed<'de> for SceneDeserializer<'a> { { deserializer.deserialize_struct( SCENE_STRUCT, - &[SCENE_ENTITIES], + &[SCENE_RESOURCES, SCENE_ENTITIES], SceneVisitor { type_registry: self.type_registry, }, @@ -163,9 +172,18 @@ impl<'a, 'de> Visitor<'de> for SceneVisitor<'a> { where A: MapAccess<'de>, { + let mut resources = None; let mut entities = None; while let Some(key) = map.next_key()? { match key { + SceneField::Resources => { + if resources.is_some() { + return Err(Error::duplicate_field(SCENE_RESOURCES)); + } + resources = Some(map.next_value_seed(SceneMapDeserializer { + registry: self.type_registry, + })?); + } SceneField::Entities => { if entities.is_some() { return Err(Error::duplicate_field(SCENE_ENTITIES)); @@ -177,22 +195,35 @@ impl<'a, 'de> Visitor<'de> for SceneVisitor<'a> { } } + let resources = resources.ok_or_else(|| Error::missing_field(SCENE_RESOURCES))?; let entities = entities.ok_or_else(|| Error::missing_field(SCENE_ENTITIES))?; - Ok(DynamicScene { entities }) + Ok(DynamicScene { + resources, + entities, + }) } fn visit_seq(self, mut seq: A) -> Result where A: SeqAccess<'de>, { + let resources = seq + .next_element_seed(SceneMapDeserializer { + registry: self.type_registry, + })? + .ok_or_else(|| Error::missing_field(SCENE_RESOURCES))?; + let entities = seq .next_element_seed(SceneEntitiesDeserializer { type_registry: self.type_registry, })? .ok_or_else(|| Error::missing_field(SCENE_ENTITIES))?; - Ok(DynamicScene { entities }) + Ok(DynamicScene { + resources, + entities, + }) } } @@ -281,7 +312,7 @@ impl<'a, 'de> Visitor<'de> for SceneEntityVisitor<'a> { A: SeqAccess<'de>, { let components = seq - .next_element_seed(ComponentDeserializer { + .next_element_seed(SceneMapDeserializer { registry: self.registry, })? .ok_or_else(|| Error::missing_field(ENTITY_FIELD_COMPONENTS))?; @@ -304,7 +335,7 @@ impl<'a, 'de> Visitor<'de> for SceneEntityVisitor<'a> { return Err(Error::duplicate_field(ENTITY_FIELD_COMPONENTS)); } - components = Some(map.next_value_seed(ComponentDeserializer { + components = Some(map.next_value_seed(SceneMapDeserializer { registry: self.registry, })?); } @@ -321,32 +352,32 @@ impl<'a, 'de> Visitor<'de> for SceneEntityVisitor<'a> { } } -pub struct ComponentDeserializer<'a> { +pub struct SceneMapDeserializer<'a> { pub registry: &'a TypeRegistry, } -impl<'a, 'de> DeserializeSeed<'de> for ComponentDeserializer<'a> { +impl<'a, 'de> DeserializeSeed<'de> for SceneMapDeserializer<'a> { type Value = Vec>; fn deserialize(self, deserializer: D) -> Result where D: serde::Deserializer<'de>, { - deserializer.deserialize_map(ComponentVisitor { + deserializer.deserialize_map(SceneMapVisitor { registry: self.registry, }) } } -struct ComponentVisitor<'a> { +struct SceneMapVisitor<'a> { pub registry: &'a TypeRegistry, } -impl<'a, 'de> Visitor<'de> for ComponentVisitor<'a> { +impl<'a, 'de> Visitor<'de> for SceneMapVisitor<'a> { type Value = Vec>; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("map of components") + formatter.write_str("map of reflect types") } fn visit_map(self, mut map: A) -> std::result::Result @@ -354,23 +385,23 @@ impl<'a, 'de> Visitor<'de> for ComponentVisitor<'a> { A: MapAccess<'de>, { let mut added = HashSet::new(); - let mut components = Vec::new(); + let mut entries = Vec::new(); while let Some(registration) = map.next_key_seed(TypeRegistrationDeserializer::new(self.registry))? { if !added.insert(registration.type_id()) { return Err(Error::custom(format_args!( - "duplicate component: `{}`", + "duplicate reflect type: `{}`", registration.type_name() ))); } - components.push( + entries.push( map.next_value_seed(TypedReflectDeserializer::new(registration, self.registry))?, ); } - Ok(components) + Ok(entries) } fn visit_seq(self, mut seq: A) -> Result @@ -394,7 +425,7 @@ mod tests { use crate::{DynamicScene, DynamicSceneBuilder}; use bevy_app::AppTypeRegistry; use bevy_ecs::entity::EntityMap; - use bevy_ecs::prelude::{Component, ReflectComponent, World}; + use bevy_ecs::prelude::{Component, ReflectComponent, ReflectResource, Resource, World}; use bevy_reflect::{FromReflect, Reflect, ReflectSerialize}; use bincode::Options; use serde::de::DeserializeSeed; @@ -429,6 +460,12 @@ mod tests { }, } + #[derive(Resource, Reflect, Default)] + #[reflect(Resource)] + struct MyResource { + foo: i32, + } + fn create_world() -> World { let mut world = World::new(); let registry = AppTypeRegistry::default(); @@ -443,6 +480,7 @@ mod tests { registry.register_type_data::(); registry.register::<[usize; 3]>(); registry.register::<(f32, f32)>(); + registry.register::(); } world.insert_resource(registry); world @@ -456,11 +494,19 @@ mod tests { let b = world.spawn((Foo(123), Bar(345))).id(); let c = world.spawn((Foo(123), Bar(345), Baz(789))).id(); + world.insert_resource(MyResource { foo: 123 }); + let mut builder = DynamicSceneBuilder::from_world(&world); builder.extract_entities([a, b, c].into_iter()); + builder.extract_resources(); let scene = builder.build(); let expected = r#"( + resources: { + "bevy_scene::serde::tests::MyResource": ( + foo: 123, + ), + }, entities: { 0: ( components: { @@ -493,6 +539,11 @@ mod tests { let world = create_world(); let input = r#"( + resources: { + "bevy_scene::serde::tests::MyResource": ( + foo: 123, + ), + }, entities: { 0: ( components: { @@ -520,6 +571,11 @@ mod tests { }; let scene = scene_deserializer.deserialize(&mut deserializer).unwrap(); + assert_eq!( + 1, + scene.resources.len(), + "expected `resources` to contain 1 resource" + ); assert_eq!( 3, scene.entities.len(), @@ -530,6 +586,11 @@ mod tests { let mut dst_world = create_world(); scene.write_to_world(&mut dst_world, &mut map).unwrap(); + let my_resource = dst_world.get_resource::(); + assert!(my_resource.is_some()); + let my_resource = my_resource.unwrap(); + assert_eq!(my_resource.foo, 123); + assert_eq!(3, dst_world.query::<&Foo>().iter(&dst_world).count()); assert_eq!(2, dst_world.query::<&Bar>().iter(&dst_world).count()); assert_eq!(1, dst_world.query::<&Baz>().iter(&dst_world).count()); @@ -554,10 +615,10 @@ mod tests { assert_eq!( vec![ - 1, 0, 1, 37, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, 58, 58, 115, 101, 114, - 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, 67, 111, 109, 112, 111, - 110, 101, 110, 116, 1, 2, 3, 102, 102, 166, 63, 205, 204, 108, 64, 1, 12, 72, 101, - 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 + 0, 1, 0, 1, 37, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, 58, 58, 115, 101, + 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, 67, 111, 109, 112, + 111, 110, 101, 110, 116, 1, 2, 3, 102, 102, 166, 63, 205, 204, 108, 64, 1, 12, 72, + 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 ], serialized_scene ); @@ -594,11 +655,11 @@ mod tests { assert_eq!( vec![ - 145, 129, 0, 145, 129, 217, 37, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, 58, - 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, 67, - 111, 109, 112, 111, 110, 101, 110, 116, 147, 147, 1, 2, 3, 146, 202, 63, 166, 102, - 102, 202, 64, 108, 204, 205, 129, 165, 84, 117, 112, 108, 101, 172, 72, 101, 108, - 108, 111, 32, 87, 111, 114, 108, 100, 33 + 146, 128, 129, 0, 145, 129, 217, 37, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, + 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, + 67, 111, 109, 112, 111, 110, 101, 110, 116, 147, 147, 1, 2, 3, 146, 202, 63, 166, + 102, 102, 202, 64, 108, 204, 205, 129, 165, 84, 117, 112, 108, 101, 172, 72, 101, + 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 ], buf ); @@ -635,12 +696,12 @@ mod tests { assert_eq!( vec![ - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, - 0, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, 58, 58, 115, 101, 114, 100, 101, - 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, 67, 111, 109, 112, 111, 110, 101, - 110, 116, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, - 102, 102, 166, 63, 205, 204, 108, 64, 1, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 72, 101, - 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 37, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, 58, 58, + 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, 67, 111, + 109, 112, 111, 110, 101, 110, 116, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, + 3, 0, 0, 0, 0, 0, 0, 0, 102, 102, 166, 63, 205, 204, 108, 64, 1, 0, 0, 0, 12, 0, 0, + 0, 0, 0, 0, 0, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 ], serialized_scene ); diff --git a/crates/bevy_sprite/src/collide_aabb.rs b/crates/bevy_sprite/src/collide_aabb.rs index fa49f13aed872..f455c9fe4a3cd 100644 --- a/crates/bevy_sprite/src/collide_aabb.rs +++ b/crates/bevy_sprite/src/collide_aabb.rs @@ -2,7 +2,7 @@ use bevy_math::{Vec2, Vec3}; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum Collision { Left, Right, diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index d7a02c3ff780b..b624b7bad888e 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -34,7 +34,7 @@ use bevy_reflect::TypeUuid; use bevy_render::{ render_phase::AddRenderCommand, render_resource::{Shader, SpecializedRenderPipelines}, - ExtractSchedule, RenderApp, RenderSet, + ExtractSchedule, Render, RenderApp, RenderSet, }; #[derive(Default)] @@ -71,13 +71,14 @@ impl Plugin for SpritePlugin { .init_resource::() .add_render_command::() .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::), diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 4ee87cff02ac9..ed03db9a48484 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -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::>() .init_resource::>() .init_resource::>>() - .add_systems(( - extract_materials_2d::.in_schedule(ExtractSchedule), - prepare_materials_2d:: - .in_set(RenderSet::Prepare) - .after(PrepareAssetSet::PreAssetPrepare), - queue_material2d_meshes::.in_set(RenderSet::Queue), - )); + .add_systems(ExtractSchedule, extract_materials_2d::) + .add_systems( + Render, + ( + prepare_materials_2d:: + .in_set(RenderSet::Prepare) + .after(PrepareAssetSet::PreAssetPrepare), + queue_material2d_meshes::.in_set(RenderSet::Queue), + ), + ); } } } diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index a001b00dc3a8c..070898df554e9 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -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::() .init_resource::>() - .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), + ), + ); } } } diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 767b9264909cb..e740e525a2682 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -79,9 +79,9 @@ impl Plugin for TextPlugin { .init_resource::() .init_resource::() .insert_resource(TextPipeline::default()) - .add_system( + .add_systems( + PostUpdate, update_text2d_layout - .in_base_set(CoreSet::PostUpdate) // Potential conflict: `Assets` // 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), ); } } diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index aac6c400feac2..f252406823639 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -23,8 +23,8 @@ use bevy_utils::HashSet; use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged}; use crate::{ - Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextPipeline, - TextSettings, YAxisOrientation, + Font, FontAtlasSet, FontAtlasWarning, PositionedGlyph, Text, TextError, TextLayoutInfo, + TextPipeline, TextSettings, YAxisOrientation, }; /// The maximum width and height of text. The text will wrap according to the specified size. @@ -94,48 +94,42 @@ pub fn extract_text2d_sprite( .get_single() .map(|window| window.resolution.scale_factor() as f32) .unwrap_or(1.0); + let scaling = GlobalTransform::from_scale(Vec3::splat(scale_factor.recip())); - for (entity, computed_visibility, text, text_layout_info, anchor, text_transform) in + for (entity, computed_visibility, text, text_layout_info, anchor, global_transform) in text2d_query.iter() { if !computed_visibility.is_visible() { continue; } - let text_glyphs = &text_layout_info.glyphs; let text_anchor = -(anchor.as_vec() + 0.5); - let alignment_offset = text_layout_info.size * text_anchor; + let alignment_translation = text_layout_info.size * text_anchor; + let transform = *global_transform + * scaling + * GlobalTransform::from_translation(alignment_translation.extend(0.)); let mut color = Color::WHITE; let mut current_section = usize::MAX; - for text_glyph in text_glyphs { - if text_glyph.section_index != current_section { - color = text.sections[text_glyph.section_index] - .style - .color - .as_rgba_linear(); - current_section = text_glyph.section_index; + for PositionedGlyph { + position, + atlas_info, + section_index, + .. + } in &text_layout_info.glyphs + { + if *section_index != current_section { + color = text.sections[*section_index].style.color.as_rgba_linear(); + current_section = *section_index; } - let atlas = texture_atlases - .get(&text_glyph.atlas_info.texture_atlas) - .unwrap(); - let handle = atlas.texture.clone_weak(); - let index = text_glyph.atlas_info.glyph_index; - let rect = Some(atlas.textures[index]); - - let glyph_transform = - Transform::from_translation((alignment_offset + text_glyph.position).extend(0.)); - - let transform = *text_transform - * GlobalTransform::from_scale(Vec3::splat(scale_factor.recip())) - * glyph_transform; + let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap(); extracted_sprites.sprites.push(ExtractedSprite { entity, - transform, + transform: transform * GlobalTransform::from_translation(position.extend(0.)), color, - rect, + rect: Some(atlas.textures[atlas_info.glyph_index]), custom_size: None, - image_handle_id: handle.id(), + image_handle_id: atlas.texture.id(), flip_x: false, flip_y: false, anchor: Anchor::Center.as_vec(), diff --git a/crates/bevy_time/src/common_conditions.rs b/crates/bevy_time/src/common_conditions.rs index 357f83cbe2dcc..974de24dfae2c 100644 --- a/crates/bevy_time/src/common_conditions.rs +++ b/crates/bevy_time/src/common_conditions.rs @@ -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() { @@ -32,7 +32,7 @@ use bevy_utils::Duration; /// For more accurate timers, use the [`Timer`] class directly (see /// [`Timer::times_finished_this_tick`] to address the problem mentioned above), or /// use fixed timesteps that allow systems to run multiple times per frame. -pub fn on_timer(duration: Duration) -> impl FnMut(Res