diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00a5dd8040c700..c5b282b74a27c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -108,6 +108,7 @@ jobs: ~/.cargo/git/db/ target/ crates/bevy_ecs_compile_fail_tests/target/ + crates/bevy_reflect_compile_fail_tests/target/ key: ${{ runner.os }}-cargo-check-compiles-${{ hashFiles('**/Cargo.toml') }} - uses: dtolnay/rust-toolchain@stable with: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8365aa1864483c..ad7f58a9b1ef89 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -275,6 +275,26 @@ By giving feedback on this work (and related supporting work), you can help us m Finally, if nothing brings you more satisfaction than seeing every last issue labeled and all resolved issues closed, feel free to message @cart for a Bevy org role to help us keep things tidy. As discussed in [*How we're organized*](#how-were-organized), this role only requires good faith and a basic understanding of our development process. +### How to adopt pull requests + +Occasionally authors of pull requests get busy or become unresponsive, or project members fail to reply in a timely manner. +This is a natural part of any open source project. +To avoid blocking these efforts, these pull requests may be *adopted*, where another contributor creates a new pull request with the same content. +If there is an old pull request that is without updates, comment to the organization whether it is appropriate to add the +*[S-Adopt-Me](https://github.com/bevyengine/bevy/labels/S-Adopt-Me)* label, to indicate that it can be *adopted*. +If you plan on adopting a PR yourself, you can also leave a comment on the PR asking the author if they plan on returning. +If the author gives permission or simply doesn't respond after a few days, then it can be adopted. +This may sometimes even skip the labeling process since at that point the PR has been adopted by you. + +With this label added, it's best practice to fork the original author's branch. +This ensures that they still get credit for working on it and that the commit history is retained. +When the new pull request is ready, it should reference the original PR in the description. +Then notify org members to close the original. + +* For example, you can reference the original PR by adding the following to your PR description: + +`Adopted #number-original-pull-request` + ### Maintaining code Maintainers can merge uncontroversial pull requests that have at least two approvals (or at least one for trivial changes). diff --git a/Cargo.toml b/Cargo.toml index aa2eb4571a7fd1..11e7cc5fb06d98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ readme = "README.md" repository = "https://github.com/bevyengine/bevy" [workspace] -exclude = ["benches", "crates/bevy_ecs_compile_fail_tests"] +exclude = ["benches", "crates/bevy_ecs_compile_fail_tests", "crates/bevy_reflect_compile_fail_tests"] members = [ "crates/*", "examples/android", @@ -1358,7 +1358,7 @@ wasm = true # Tools [[example]] name = "scene_viewer" -path = "examples/tools/scene_viewer.rs" +path = "examples/tools/scene_viewer/main.rs" [package.metadata.example.scene_viewer] name = "Scene Viewer" @@ -1608,3 +1608,8 @@ inherits = "release" opt-level = "z" lto = "fat" codegen-units = 1 + +[profile.stress-test] +inherits = "release" +lto = "fat" +panic = "abort" diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index d88a82059126c9..16caea1be6c478 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -73,6 +73,8 @@ pub struct App { 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, } impl Debug for App { @@ -136,6 +138,7 @@ impl App { sub_apps: HashMap::default(), plugin_registry: Vec::default(), plugin_name_added: Default::default(), + is_building_plugin: false, } } @@ -161,11 +164,18 @@ impl App { /// /// Finalizes the [`App`] configuration. For general usage, see the example on the item /// level documentation. + /// + /// # Panics + /// + /// Panics if called from `Plugin::build()`, because it would prevent other plugins to properly build. pub fn run(&mut self) { #[cfg(feature = "trace")] 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."); + } let runner = std::mem::replace(&mut app.runner, Box::new(run_once)); (runner)(app); } @@ -858,7 +868,9 @@ impl App { plugin_name: plugin.name().to_string(), })?; } + self.is_building_plugin = true; plugin.build(self); + self.is_building_plugin = false; self.plugin_registry.push(plugin); Ok(self) } @@ -1105,4 +1117,16 @@ mod tests { fn can_add_twice_the_same_plugin_not_unique() { App::new().add_plugin(PluginD).add_plugin(PluginD); } + + #[test] + #[should_panic] + fn cant_call_app_run_from_plugin_build() { + struct PluginRun; + impl Plugin for PluginRun { + fn build(&self, app: &mut crate::App) { + app.run(); + } + } + App::new().add_plugin(PluginRun); + } } 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 f4006822f3436b..2a262b426195ba 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 @@ -3,10 +3,11 @@ use crate::{ core_2d::{camera_2d::Camera2d, Transparent2d}, }; use bevy_ecs::prelude::*; +use bevy_render::render_phase::TrackedRenderPass; use bevy_render::{ camera::ExtractedCamera, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, - render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass}, + render_phase::RenderPhase, render_resource::{LoadOp, Operations, RenderPassDescriptor}, renderer::RenderContext, view::{ExtractedView, ViewTarget}, @@ -77,21 +78,16 @@ impl Node for MainPass2dNode { depth_stencil_attachment: None, }; - let draw_functions = world.resource::>(); - let render_pass = render_context .command_encoder .begin_render_pass(&pass_descriptor); + let mut render_pass = TrackedRenderPass::new(render_pass); - let mut draw_functions = draw_functions.write(); - let mut tracked_pass = TrackedRenderPass::new(render_pass); if let Some(viewport) = camera.viewport.as_ref() { - tracked_pass.set_camera_viewport(viewport); - } - for item in &transparent_phase.items { - let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); - draw_function.draw(world, &mut tracked_pass, view_entity, item); + render_pass.set_camera_viewport(viewport); } + + transparent_phase.render(&mut render_pass, world, view_entity); } // WebGL2 quirk: if ending with a render pass with a custom viewport, the viewport isn't diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 49636f267c8a5e..f391a49496a041 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -27,7 +27,7 @@ use bevy_render::{ render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType}, render_phase::{ batch_phase_system, sort_phase_system, BatchedPhaseItem, CachedRenderPipelinePhaseItem, - DrawFunctionId, DrawFunctions, EntityPhaseItem, PhaseItem, RenderPhase, + DrawFunctionId, DrawFunctions, PhaseItem, RenderPhase, }, render_resource::CachedRenderPipelineId, Extract, RenderApp, RenderStage, @@ -115,6 +115,11 @@ pub struct Transparent2d { impl PhaseItem for Transparent2d { type SortKey = FloatOrd; + #[inline] + fn entity(&self) -> Entity { + self.entity + } + #[inline] fn sort_key(&self) -> Self::SortKey { self.sort_key @@ -131,13 +136,6 @@ impl PhaseItem for Transparent2d { } } -impl EntityPhaseItem for Transparent2d { - #[inline] - fn entity(&self) -> Entity { - self.entity - } -} - impl CachedRenderPipelinePhaseItem for Transparent2d { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { 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 cf3a3d38fa6032..1067643e03b129 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 @@ -3,10 +3,11 @@ use crate::{ core_3d::{AlphaMask3d, Camera3d, Opaque3d, Transparent3d}, }; use bevy_ecs::prelude::*; +use bevy_render::render_phase::TrackedRenderPass; use bevy_render::{ camera::ExtractedCamera, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, - render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass}, + render_phase::RenderPhase, render_resource::{LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor}, renderer::RenderContext, view::{ExtractedView, ViewDepthTexture, ViewTarget}, @@ -95,20 +96,16 @@ impl Node for MainPass3dNode { }), }; - let draw_functions = world.resource::>(); - let render_pass = render_context .command_encoder .begin_render_pass(&pass_descriptor); - let mut draw_functions = draw_functions.write(); - let mut tracked_pass = TrackedRenderPass::new(render_pass); + let mut render_pass = TrackedRenderPass::new(render_pass); + if let Some(viewport) = camera.viewport.as_ref() { - tracked_pass.set_camera_viewport(viewport); - } - for item in &opaque_phase.items { - let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); - draw_function.draw(world, &mut tracked_pass, view_entity, item); + render_pass.set_camera_viewport(viewport); } + + opaque_phase.render(&mut render_pass, world, view_entity); } if !alpha_mask_phase.items.is_empty() { @@ -134,20 +131,16 @@ impl Node for MainPass3dNode { }), }; - let draw_functions = world.resource::>(); - let render_pass = render_context .command_encoder .begin_render_pass(&pass_descriptor); - let mut draw_functions = draw_functions.write(); - let mut tracked_pass = TrackedRenderPass::new(render_pass); + let mut render_pass = TrackedRenderPass::new(render_pass); + if let Some(viewport) = camera.viewport.as_ref() { - tracked_pass.set_camera_viewport(viewport); - } - for item in &alpha_mask_phase.items { - let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); - draw_function.draw(world, &mut tracked_pass, view_entity, item); + render_pass.set_camera_viewport(viewport); } + + alpha_mask_phase.render(&mut render_pass, world, view_entity); } if !transparent_phase.items.is_empty() { @@ -178,20 +171,16 @@ impl Node for MainPass3dNode { }), }; - let draw_functions = world.resource::>(); - let render_pass = render_context .command_encoder .begin_render_pass(&pass_descriptor); - let mut draw_functions = draw_functions.write(); - let mut tracked_pass = TrackedRenderPass::new(render_pass); + let mut render_pass = TrackedRenderPass::new(render_pass); + if let Some(viewport) = camera.viewport.as_ref() { - tracked_pass.set_camera_viewport(viewport); - } - for item in &transparent_phase.items { - let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); - draw_function.draw(world, &mut tracked_pass, view_entity, item); + render_pass.set_camera_viewport(viewport); } + + transparent_phase.render(&mut render_pass, world, view_entity); } // WebGL2 quirk: if ending with a render pass with a custom viewport, the viewport isn't diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 8dc51d760ac1c5..34a430a3453980 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -29,8 +29,8 @@ use bevy_render::{ prelude::Msaa, render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType}, render_phase::{ - sort_phase_system, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, - EntityPhaseItem, PhaseItem, RenderPhase, + sort_phase_system, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, + RenderPhase, }, render_resource::{ CachedRenderPipelineId, Extent3d, TextureDescriptor, TextureDimension, TextureFormat, @@ -124,6 +124,11 @@ impl PhaseItem for Opaque3d { // NOTE: Values increase towards the camera. Front-to-back ordering for opaque means we need a descending sort. type SortKey = Reverse; + #[inline] + fn entity(&self) -> Entity { + self.entity + } + #[inline] fn sort_key(&self) -> Self::SortKey { Reverse(FloatOrd(self.distance)) @@ -141,13 +146,6 @@ impl PhaseItem for Opaque3d { } } -impl EntityPhaseItem for Opaque3d { - #[inline] - fn entity(&self) -> Entity { - self.entity - } -} - impl CachedRenderPipelinePhaseItem for Opaque3d { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { @@ -166,6 +164,11 @@ impl PhaseItem for AlphaMask3d { // NOTE: Values increase towards the camera. Front-to-back ordering for alpha mask means we need a descending sort. type SortKey = Reverse; + #[inline] + fn entity(&self) -> Entity { + self.entity + } + #[inline] fn sort_key(&self) -> Self::SortKey { Reverse(FloatOrd(self.distance)) @@ -183,13 +186,6 @@ impl PhaseItem for AlphaMask3d { } } -impl EntityPhaseItem for AlphaMask3d { - #[inline] - fn entity(&self) -> Entity { - self.entity - } -} - impl CachedRenderPipelinePhaseItem for AlphaMask3d { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { @@ -208,6 +204,11 @@ impl PhaseItem for Transparent3d { // NOTE: Values increase towards the camera. Back-to-front ordering for transparent means we need an ascending sort. type SortKey = FloatOrd; + #[inline] + fn entity(&self) -> Entity { + self.entity + } + #[inline] fn sort_key(&self) -> Self::SortKey { FloatOrd(self.distance) @@ -224,13 +225,6 @@ impl PhaseItem for Transparent3d { } } -impl EntityPhaseItem for Transparent3d { - #[inline] - fn entity(&self) -> Entity { - self.entity - } -} - impl CachedRenderPipelinePhaseItem for Transparent3d { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { diff --git a/crates/bevy_derive/src/bevy_main.rs b/crates/bevy_derive/src/bevy_main.rs index 6ec1363097f259..fc5ea60bb0a1fd 100644 --- a/crates/bevy_derive/src/bevy_main.rs +++ b/crates/bevy_derive/src/bevy_main.rs @@ -10,7 +10,7 @@ pub fn bevy_main(_attr: TokenStream, item: TokenStream) -> TokenStream { ); TokenStream::from(quote! { - // use ndk-glue macro to create an activity: https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-macro + // use ndk-glue macro to create an activity: https://github.com/rust-mobile/ndk-glue/tree/main/ndk-macro #[cfg(target_os = "android")] #[cfg_attr(target_os = "android", bevy::ndk_glue::main(backtrace = "on", ndk_glue = "bevy::ndk_glue"))] fn android_main() { diff --git a/crates/bevy_diagnostic/Cargo.toml b/crates/bevy_diagnostic/Cargo.toml index 2b8a1f2fa0a29c..7bd1249cbefaa9 100644 --- a/crates/bevy_diagnostic/Cargo.toml +++ b/crates/bevy_diagnostic/Cargo.toml @@ -17,3 +17,14 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.9.0" } bevy_log = { path = "../bevy_log", version = "0.9.0" } bevy_time = { path = "../bevy_time", version = "0.9.0" } bevy_utils = { path = "../bevy_utils", version = "0.9.0" } + +# MacOS +[target.'cfg(all(target_os="macos"))'.dependencies] +# Some features of sysinfo are not supported by apple. This will disable those features on apple devices +sysinfo = { version = "0.27.1", default-features = false, features = [ + "apple-app-store", +] } + +# Only include when not bevy_dynamic_plugin and on linux/windows/android +[target.'cfg(any(target_os = "linux", target_os = "windows", target_os = "android"))'.dependencies] +sysinfo = { version = "0.27.1", default-features = false } diff --git a/crates/bevy_diagnostic/src/lib.rs b/crates/bevy_diagnostic/src/lib.rs index 076618bcde060b..b2d127f1156001 100644 --- a/crates/bevy_diagnostic/src/lib.rs +++ b/crates/bevy_diagnostic/src/lib.rs @@ -2,12 +2,14 @@ mod diagnostic; mod entity_count_diagnostics_plugin; mod frame_time_diagnostics_plugin; mod log_diagnostics_plugin; +mod system_information_diagnostics_plugin; + +use bevy_app::prelude::*; pub use diagnostic::*; pub use entity_count_diagnostics_plugin::EntityCountDiagnosticsPlugin; pub use frame_time_diagnostics_plugin::FrameTimeDiagnosticsPlugin; pub use log_diagnostics_plugin::LogDiagnosticsPlugin; - -use bevy_app::prelude::*; +pub use system_information_diagnostics_plugin::SystemInformationDiagnosticsPlugin; /// Adds core diagnostics resources to an App. #[derive(Default)] @@ -15,7 +17,8 @@ pub struct DiagnosticsPlugin; impl Plugin for DiagnosticsPlugin { fn build(&self, app: &mut App) { - app.init_resource::(); + app.init_resource::() + .add_startup_system(system_information_diagnostics_plugin::internal::log_system_info); } } diff --git a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs new file mode 100644 index 00000000000000..464fc06693da07 --- /dev/null +++ b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs @@ -0,0 +1,159 @@ +use crate::DiagnosticId; +use bevy_app::{App, Plugin}; + +/// Adds a System Information Diagnostic, specifically `cpu_usage` (in %) and `mem_usage` (in %) +/// +/// Supported targets: +/// * linux, +/// * windows, +/// * android, +/// * macos +/// +/// NOT supported when using the `bevy/dynamic` feature even when using previously mentioned targets +#[derive(Default)] +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); + } +} + +impl SystemInformationDiagnosticsPlugin { + pub const CPU_USAGE: DiagnosticId = + DiagnosticId::from_u128(78494871623549551581510633532637320956); + pub const MEM_USAGE: DiagnosticId = + DiagnosticId::from_u128(42846254859293759601295317811892519825); +} + +// NOTE: sysinfo fails to compile when using bevy dynamic or on iOS and does nothing on wasm +#[cfg(all( + any( + target_os = "linux", + target_os = "windows", + target_os = "android", + target_os = "macos" + ), + not(feature = "bevy_dynamic_plugin") +))] +pub mod internal { + use bevy_ecs::{prelude::ResMut, system::Local}; + use bevy_log::info; + use sysinfo::{CpuExt, System, SystemExt}; + + use crate::{Diagnostic, Diagnostics}; + + const BYTES_TO_GIB: f64 = 1.0 / 1024.0 / 1024.0 / 1024.0; + + pub(crate) fn setup_system(mut diagnostics: ResMut) { + diagnostics.add( + Diagnostic::new( + super::SystemInformationDiagnosticsPlugin::CPU_USAGE, + "cpu_usage", + 20, + ) + .with_suffix("%"), + ); + diagnostics.add( + Diagnostic::new( + super::SystemInformationDiagnosticsPlugin::MEM_USAGE, + "mem_usage", + 20, + ) + .with_suffix("%"), + ); + } + + pub(crate) fn diagnostic_system( + mut diagnostics: ResMut, + mut sysinfo: Local>, + ) { + if sysinfo.is_none() { + *sysinfo = Some(System::new_all()); + } + let Some(sys) = sysinfo.as_mut() else { + return; + }; + + sys.refresh_cpu(); + sys.refresh_memory(); + let current_cpu_usage = { + let mut usage = 0.0; + let cpus = sys.cpus(); + for cpu in cpus { + usage += cpu.cpu_usage(); // NOTE: this returns a value from 0.0 to 100.0 + } + // average + usage / cpus.len() as f32 + }; + // `memory()` fns return a value in bytes + let total_mem = sys.total_memory() as f64 / BYTES_TO_GIB; + let used_mem = sys.used_memory() as f64 / BYTES_TO_GIB; + let current_used_mem = used_mem / total_mem * 100.0; + + diagnostics.add_measurement(super::SystemInformationDiagnosticsPlugin::CPU_USAGE, || { + current_cpu_usage as f64 + }); + diagnostics.add_measurement(super::SystemInformationDiagnosticsPlugin::MEM_USAGE, || { + current_used_mem + }); + } + + #[derive(Debug)] + // This is required because the Debug trait doesn't detect it's used when it's only used in a print :( + #[allow(dead_code)] + struct SystemInfo { + os: String, + kernel: String, + cpu: String, + core_count: String, + memory: String, + } + + pub(crate) fn log_system_info() { + let mut sys = sysinfo::System::new(); + sys.refresh_cpu(); + sys.refresh_memory(); + + let info = SystemInfo { + os: sys + .long_os_version() + .unwrap_or_else(|| String::from("not available")), + kernel: sys + .kernel_version() + .unwrap_or_else(|| String::from("not available")), + cpu: sys.global_cpu_info().brand().trim().to_string(), + core_count: sys + .physical_core_count() + .map(|x| x.to_string()) + .unwrap_or_else(|| String::from("not available")), + // Convert from Bytes to GibiBytes since it's probably what people expect most of the time + memory: format!("{:.1} GiB", sys.total_memory() as f64 * BYTES_TO_GIB), + }; + + info!("{:?}", info); + } +} + +#[cfg(not(all( + any( + target_os = "linux", + target_os = "windows", + target_os = "android", + target_os = "macos" + ), + not(feature = "bevy_dynamic_plugin") +)))] +pub mod internal { + pub(crate) fn setup_system() { + bevy_log::warn!("This platform and/or configuration is not supported!"); + } + + pub(crate) fn diagnostic_system() { + // no-op + } + + pub(crate) fn log_system_info() { + // no-op + } +} diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index b8674daf0095fd..1a0d2f0c4770d8 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -10,12 +10,12 @@ use proc_macro2::Span; use quote::{format_ident, quote}; use syn::{ parse::{Parse, ParseStream}, - parse_macro_input, + parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, - DeriveInput, Field, GenericParam, Ident, Index, LitInt, Meta, MetaList, NestedMeta, Result, - Token, TypeParam, + ConstParam, DeriveInput, Field, GenericParam, Ident, Index, LitInt, Meta, MetaList, NestedMeta, + Result, Token, TypeParam, }; struct AllTuples { @@ -222,7 +222,17 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream { for (i, param) in params.iter().enumerate() { let fn_name = Ident::new(&format!("p{i}"), Span::call_site()); let index = Index::from(i); + let ordinal = match i { + 1 => "1st".to_owned(), + 2 => "2nd".to_owned(), + 3 => "3rd".to_owned(), + x => format!("{x}th"), + }; + let comment = + format!("Gets exclusive access to the {ordinal} parameter in this [`ParamSet`]."); param_fn_muts.push(quote! { + #[doc = #comment] + /// No other parameters may be accessed while this one is active. pub fn #fn_name<'a>(&'a mut self) -> SystemParamItem<'a, 'a, #param> { // SAFETY: systems run without conflicts with other systems. // Conflicting params in ParamSet are not accessible at the same time @@ -327,13 +337,14 @@ static SYSTEM_PARAM_ATTRIBUTE_NAME: &str = "system_param"; #[proc_macro_derive(SystemParam, attributes(system_param))] pub fn derive_system_param(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); - let fields = match get_named_struct_fields(&ast.data) { - Ok(fields) => &fields.named, - Err(e) => return e.into_compile_error().into(), + let syn::Data::Struct(syn::DataStruct { fields: field_definitions, ..}) = ast.data else { + return syn::Error::new(ast.span(), "Invalid `SystemParam` type: expected a `struct`") + .into_compile_error() + .into(); }; let path = bevy_ecs_path(); - let field_attributes = fields + let field_attributes = field_definitions .iter() .map(|field| { ( @@ -358,8 +369,8 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { ) }) .collect::>(); + let mut field_locals = Vec::new(); let mut fields = Vec::new(); - let mut field_indices = Vec::new(); let mut field_types = Vec::new(); let mut ignored_fields = Vec::new(); let mut ignored_field_types = Vec::new(); @@ -368,9 +379,16 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { ignored_fields.push(field.ident.as_ref().unwrap()); ignored_field_types.push(&field.ty); } else { - fields.push(field.ident.as_ref().unwrap()); + field_locals.push(format_ident!("f{i}")); + let i = Index::from(i); + fields.push( + field + .ident + .as_ref() + .map(|f| quote! { #f }) + .unwrap_or_else(|| quote! { #i }), + ); field_types.push(&field.ty); - field_indices.push(Index::from(i)); } } @@ -398,7 +416,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { let lifetimeless_generics: Vec<_> = generics .params .iter() - .filter(|g| matches!(g, GenericParam::Type(_))) + .filter(|g| !matches!(g, GenericParam::Lifetime(_))) .collect(); let mut punctuated_generics = Punctuated::<_, Token![,]>::new(); @@ -407,15 +425,48 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { default: None, ..g.clone() }), + GenericParam::Const(g) => GenericParam::Const(ConstParam { + default: None, + ..g.clone() + }), _ => unreachable!(), })); + let mut punctuated_generics_no_bounds = punctuated_generics.clone(); + for g in &mut punctuated_generics_no_bounds { + match g { + GenericParam::Type(g) => g.bounds.clear(), + GenericParam::Lifetime(g) => g.bounds.clear(), + GenericParam::Const(_) => {} + } + } + + let mut punctuated_type_generic_idents = Punctuated::<_, Token![,]>::new(); + punctuated_type_generic_idents.extend(lifetimeless_generics.iter().filter_map(|g| match g { + GenericParam::Type(g) => Some(&g.ident), + _ => None, + })); + let mut punctuated_generic_idents = Punctuated::<_, Token![,]>::new(); punctuated_generic_idents.extend(lifetimeless_generics.iter().map(|g| match g { GenericParam::Type(g) => &g.ident, + GenericParam::Const(g) => &g.ident, _ => unreachable!(), })); + let mut tuple_types: Vec<_> = field_types.iter().map(|x| quote! { #x }).collect(); + let mut tuple_patterns: Vec<_> = field_locals.iter().map(|x| quote! { #x }).collect(); + + // If the number of fields exceeds the 16-parameter limit, + // fold the fields into tuples of tuples until we are below the limit. + const LIMIT: usize = 16; + while tuple_types.len() > LIMIT { + let end = Vec::from_iter(tuple_types.drain(..LIMIT)); + tuple_types.push(parse_quote!( (#(#end,)*) )); + + let end = Vec::from_iter(tuple_patterns.drain(..LIMIT)); + tuple_patterns.push(parse_quote!( (#(#end,)*) )); + } // Create a where clause for the `ReadOnlySystemParam` impl. // Ensure that each field implements `ReadOnlySystemParam`. let mut read_only_generics = generics.clone(); @@ -439,15 +490,15 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { } #[doc(hidden)] - type State<'w, 's, #punctuated_generic_idents> = FetchState< - (#(<#field_types as #path::system::SystemParam>::State,)*), + type State<'w, 's, #punctuated_generics_no_bounds> = FetchState< + (#(<#tuple_types as #path::system::SystemParam>::State,)*), #punctuated_generic_idents >; #[doc(hidden)] - #state_struct_visibility struct FetchState { + #state_struct_visibility struct FetchState { state: TSystemParamState, - marker: std::marker::PhantomData(#punctuated_generic_idents)> + marker: std::marker::PhantomData(#punctuated_type_generic_idents)> } unsafe impl<'__w, '__s, #punctuated_generics> #path::system::SystemParamState for @@ -476,8 +527,11 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { world: &'w #path::world::World, change_tick: u32, ) -> Self::Item<'w, 's> { + let (#(#tuple_patterns,)*) = < + <(#(#tuple_types,)*) as #path::system::SystemParam>::State as #path::system::SystemParamState + >::get_param(&mut state.state, system_meta, world, change_tick); #struct_name { - #(#fields: <<#field_types as #path::system::SystemParam>::State as #path::system::SystemParamState>::get_param(&mut state.state.#field_indices, system_meta, world, change_tick),)* + #(#fields: #field_locals,)* #(#ignored_fields: <#ignored_field_types>::default(),)* } } diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 705ca64a7f6b09..ffd9fdc5187834 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -41,20 +41,20 @@ use std::{ /// [`Entities::get`]: crate::entity::Entities #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[repr(transparent)] -pub struct ArchetypeRow(usize); +pub struct ArchetypeRow(u32); impl ArchetypeRow { - pub const INVALID: ArchetypeRow = ArchetypeRow(usize::MAX); + pub const INVALID: ArchetypeRow = ArchetypeRow(u32::MAX); /// Creates a `ArchetypeRow`. pub const fn new(index: usize) -> Self { - Self(index) + Self(index as u32) } /// Gets the index of the row. #[inline] pub const fn index(self) -> usize { - self.0 + self.0 as usize } } @@ -69,7 +69,7 @@ impl ArchetypeRow { /// [`EMPTY`]: crate::archetype::ArchetypeId::EMPTY #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[repr(transparent)] -pub struct ArchetypeId(usize); +pub struct ArchetypeId(u32); impl ArchetypeId { /// The ID for the [`Archetype`] without any components. @@ -77,16 +77,16 @@ impl ArchetypeId { /// # Safety: /// /// This must always have an all-1s bit pattern to ensure soundness in fast entity id space allocation. - pub const INVALID: ArchetypeId = ArchetypeId(usize::MAX); + pub const INVALID: ArchetypeId = ArchetypeId(u32::MAX); #[inline] pub(crate) const fn new(index: usize) -> Self { - ArchetypeId(index) + ArchetypeId(index as u32) } #[inline] pub(crate) fn index(self) -> usize { - self.0 + self.0 as usize } } @@ -407,18 +407,18 @@ impl Archetype { /// Fetches the row in the [`Table`] where the components for the entity at `index` /// is stored. /// - /// An entity's archetype index can be fetched from [`EntityLocation::archetype_row`], which + /// An entity's archetype row can be fetched from [`EntityLocation::archetype_row`], which /// can be retrieved from [`Entities::get`]. /// /// # Panics /// This function will panic if `index >= self.len()`. /// /// [`Table`]: crate::storage::Table - /// [`EntityLocation`]: crate::entity::EntityLocation::archetype_row + /// [`EntityLocation::archetype_row`]: crate::entity::EntityLocation::archetype_row /// [`Entities::get`]: crate::entity::Entities::get #[inline] - pub fn entity_table_row(&self, index: ArchetypeRow) -> TableRow { - self.entities[index.0].table_row + pub fn entity_table_row(&self, row: ArchetypeRow) -> TableRow { + self.entities[row.index()].table_row } /// Updates if the components for the entity at `index` can be found @@ -427,8 +427,8 @@ impl Archetype { /// # Panics /// This function will panic if `index >= self.len()`. #[inline] - pub(crate) fn set_entity_table_row(&mut self, index: ArchetypeRow, table_row: TableRow) { - self.entities[index.0].table_row = table_row; + pub(crate) fn set_entity_table_row(&mut self, row: ArchetypeRow, table_row: TableRow) { + self.entities[row.index()].table_row = table_row; } /// Allocates an entity to the archetype. @@ -441,11 +441,14 @@ impl Archetype { entity: Entity, table_row: TableRow, ) -> EntityLocation { + let archetype_row = ArchetypeRow::new(self.entities.len()); self.entities.push(ArchetypeEntity { entity, table_row }); EntityLocation { archetype_id: self.id, - archetype_row: ArchetypeRow(self.entities.len() - 1), + archetype_row, + table_id: self.table_id, + table_row, } } @@ -458,14 +461,14 @@ impl Archetype { /// /// # Panics /// This function will panic if `index >= self.len()` - pub(crate) fn swap_remove(&mut self, index: ArchetypeRow) -> ArchetypeSwapRemoveResult { - let is_last = index.0 == self.entities.len() - 1; - let entity = self.entities.swap_remove(index.0); + pub(crate) fn swap_remove(&mut self, row: ArchetypeRow) -> ArchetypeSwapRemoveResult { + let is_last = row.index() == self.entities.len() - 1; + let entity = self.entities.swap_remove(row.index()); ArchetypeSwapRemoveResult { swapped_entity: if is_last { None } else { - Some(self.entities[index.0].entity) + Some(self.entities[row.index()].entity) }, table_row: entity.table_row, } @@ -691,7 +694,7 @@ impl Archetypes { .archetype_ids .entry(archetype_identity) .or_insert_with(move || { - let id = ArchetypeId(archetypes.len()); + let id = ArchetypeId::new(archetypes.len()); let table_start = *archetype_component_count; *archetype_component_count += table_components.len(); let table_archetype_components = diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 3236929f509725..259de1abed446b 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -6,7 +6,7 @@ pub use bevy_ecs_macros::Bundle; use crate::{ archetype::{ - Archetype, ArchetypeId, ArchetypeRow, Archetypes, BundleComponentStatus, ComponentStatus, + Archetype, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus, SpawnBundleStatus, }, component::{Component, ComponentId, ComponentStorage, Components, StorageType, Tick}, @@ -528,13 +528,9 @@ impl<'a, 'b> BundleInserter<'a, 'b> { pub unsafe fn insert( &mut self, entity: Entity, - archetype_row: ArchetypeRow, + location: EntityLocation, bundle: T, ) -> EntityLocation { - let location = EntityLocation { - archetype_row, - archetype_id: self.archetype.id(), - }; match &mut self.result { InsertBundleResult::SameArchetype => { // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) @@ -548,7 +544,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> { self.sparse_sets, add_bundle, entity, - self.archetype.entity_table_row(archetype_row), + location.table_row, self.change_tick, bundle, ); diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 2bd966fd1845e8..d029d9dad9bf93 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -5,7 +5,7 @@ use crate::{ ptr::PtrMut, system::Resource, }; -use bevy_ptr::UnsafeCellDeref; +use bevy_ptr::{Ptr, UnsafeCellDeref}; use std::ops::{Deref, DerefMut}; /// The (arbitrarily chosen) minimum number of world tick increments between `check_tick` scans. @@ -421,13 +421,29 @@ pub struct MutUntyped<'a> { } impl<'a> MutUntyped<'a> { - /// Returns the pointer to the value, without marking it as changed. + /// Returns the pointer to the value, marking it as changed. /// - /// In order to mark the value as changed, you need to call [`set_changed`](DetectChanges::set_changed) manually. + /// In order to avoid marking the value as changed, you need to call [`bypass_change_detection`](DetectChanges::bypass_change_detection). #[inline] - pub fn into_inner(self) -> PtrMut<'a> { + pub fn into_inner(mut self) -> PtrMut<'a> { + self.set_changed(); self.value } + + /// Returns a pointer to the value without taking ownership of this smart pointer, marking it as changed. + /// + /// In order to avoid marking the value as changed, you need to call [`bypass_change_detection`](DetectChanges::bypass_change_detection). + #[inline] + pub fn as_mut(&mut self) -> PtrMut<'_> { + self.set_changed(); + self.value.reborrow() + } + + /// Returns an immutable pointer to the value without taking ownership. + #[inline] + pub fn as_ref(&self) -> Ptr<'_> { + self.value.as_ref() + } } impl<'a> DetectChanges for MutUntyped<'a> { diff --git a/crates/bevy_ecs/src/entity/map_entities.rs b/crates/bevy_ecs/src/entity/map_entities.rs index 516588d1597c9c..2a8dbfbc468c85 100644 --- a/crates/bevy_ecs/src/entity/map_entities.rs +++ b/crates/bevy_ecs/src/entity/map_entities.rs @@ -117,4 +117,9 @@ impl EntityMap { pub fn is_empty(&self) -> bool { self.map.is_empty() } + + /// An iterator visiting all (key, value) pairs in arbitrary order. + pub fn iter(&self) -> impl Iterator + '_ { + self.map.iter().map(|(from, to)| (*from, *to)) + } } diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 82614aeafcaaa6..c7f74c5f8d0523 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -36,7 +36,7 @@ pub use map_entities::*; use crate::{ archetype::{ArchetypeId, ArchetypeRow}, - storage::SparseSetIndex, + storage::{SparseSetIndex, TableId, TableRow}, }; use serde::{Deserialize, Serialize}; use std::{convert::TryFrom, fmt, mem, sync::atomic::Ordering}; @@ -716,12 +716,17 @@ impl Entities { } } +// This type is repr(C) to ensure that the layout and values within it can be safe to fully fill +// with u8::MAX, as required by [`Entities::flush_and_reserve_invalid_assuming_no_entities`]. // Safety: // This type must not contain any pointers at any level, and be safe to fully fill with u8::MAX. +/// Metadata for an [`Entity`]. #[derive(Copy, Clone, Debug)] #[repr(C)] struct EntityMeta { + /// The current generation of the [`Entity`]. pub generation: u32, + /// The current location of the [`Entity`] pub location: EntityLocation, } @@ -731,19 +736,39 @@ impl EntityMeta { location: EntityLocation { archetype_id: ArchetypeId::INVALID, archetype_row: ArchetypeRow::INVALID, // dummy value, to be filled in + table_id: TableId::INVALID, + table_row: TableRow::INVALID, // dummy value, to be filled in }, }; } +// This type is repr(C) to ensure that the layout and values within it can be safe to fully fill +// with u8::MAX, as required by [`Entities::flush_and_reserve_invalid_assuming_no_entities`]. +// SAFETY: +// This type must not contain any pointers at any level, and be safe to fully fill with u8::MAX. /// A location of an entity in an archetype. #[derive(Copy, Clone, Debug)] #[repr(C)] pub struct EntityLocation { - /// The archetype index + /// The ID of the [`Archetype`] the [`Entity`] belongs to. + /// + /// [`Archetype`]: crate::archetype::Archetype pub archetype_id: ArchetypeId, - /// The index of the entity in the archetype + /// The index of the [`Entity`] within its [`Archetype`]. + /// + /// [`Archetype`]: crate::archetype::Archetype pub archetype_row: ArchetypeRow, + + /// The ID of the [`Table`] the [`Entity`] belongs to. + /// + /// [`Table`]: crate::storage::Table + pub table_id: TableId, + + /// The index of the [`Entity`] within its [`Table`]. + /// + /// [`Table`]: crate::storage::Table + pub table_row: TableRow, } #[cfg(test)] diff --git a/crates/bevy_ecs/src/event.rs b/crates/bevy_ecs/src/event.rs index b7ea293b211584..2b641bf48216b7 100644 --- a/crates/bevy_ecs/src/event.rs +++ b/crates/bevy_ecs/src/event.rs @@ -4,8 +4,7 @@ use crate as bevy_ecs; use crate::system::{Local, Res, ResMut, Resource, SystemParam}; use bevy_utils::tracing::trace; use std::ops::{Deref, DerefMut}; -use std::{fmt, hash::Hash, marker::PhantomData}; - +use std::{fmt, hash::Hash, iter::Chain, marker::PhantomData, slice::Iter}; /// A type that can be stored in an [`Events`] resource /// You can conveniently access events using the [`EventReader`] and [`EventWriter`] system parameter. /// @@ -194,18 +193,13 @@ impl<'w, 's, E: Event> EventReader<'w, 's, E> { /// Iterates over the events this [`EventReader`] has not seen yet. This updates the /// [`EventReader`]'s event counter, which means subsequent event reads will not include events /// that happened before now. - pub fn iter(&mut self) -> impl DoubleEndedIterator + ExactSizeIterator { - self.iter_with_id().map(|(event, _id)| event) + pub fn iter(&mut self) -> ManualEventIterator<'_, E> { + self.reader.iter(&self.events) } /// Like [`iter`](Self::iter), except also returning the [`EventId`] of the events. - pub fn iter_with_id( - &mut self, - ) -> impl DoubleEndedIterator)> + ExactSizeIterator)> - { - self.reader.iter_with_id(&self.events).inspect(|(_, id)| { - trace!("EventReader::iter() -> {}", id); - }) + pub fn iter_with_id(&mut self) -> ManualEventIteratorWithId<'_, E> { + self.reader.iter_with_id(&self.events) } /// Determines the number of events available to be read from this [`EventReader`] without consuming any. @@ -337,34 +331,16 @@ impl Default for ManualEventReader { #[allow(clippy::len_without_is_empty)] // Check fails since the is_empty implementation has a signature other than `(&self) -> bool` impl ManualEventReader { /// See [`EventReader::iter`] - pub fn iter<'a>( - &'a mut self, - events: &'a Events, - ) -> impl DoubleEndedIterator + ExactSizeIterator { - self.iter_with_id(events).map(|(e, _)| e) + pub fn iter<'a>(&'a mut self, events: &'a Events) -> ManualEventIterator<'a, E> { + self.iter_with_id(events).without_id() } /// See [`EventReader::iter_with_id`] pub fn iter_with_id<'a>( &'a mut self, events: &'a Events, - ) -> impl DoubleEndedIterator)> - + ExactSizeIterator)> { - let a_index = (self.last_event_count).saturating_sub(events.events_a.start_event_count); - let b_index = (self.last_event_count).saturating_sub(events.events_b.start_event_count); - let a = events.events_a.get(a_index..).unwrap_or_default(); - let b = events.events_b.get(b_index..).unwrap_or_default(); - - let unread_count = a.len() + b.len(); - // Ensure `len` is implemented correctly - debug_assert_eq!(unread_count, self.len(events)); - self.last_event_count = events.event_count - unread_count; - // Iterate the oldest first, then the newer events - let iterator = a.iter().chain(b.iter()); - iterator - .map(|e| (&e.event, e.event_id)) - .with_exact_size(unread_count) - .inspect(move |(_, id)| self.last_event_count = (id.id + 1).max(self.last_event_count)) + ) -> ManualEventIteratorWithId<'a, E> { + ManualEventIteratorWithId::new(self, events) } /// See [`EventReader::len`] @@ -392,57 +368,114 @@ impl ManualEventReader { } } -trait IteratorExt { - fn with_exact_size(self, len: usize) -> ExactSize - where - Self: Sized, - { - ExactSize::new(self, len) +pub struct ManualEventIterator<'a, E: Event> { + iter: ManualEventIteratorWithId<'a, E>, +} + +impl<'a, E: Event> Iterator for ManualEventIterator<'a, E> { + type Item = &'a E; + fn next(&mut self) -> Option { + self.iter.next().map(|(event, _)| event) + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() } } -impl IteratorExt for I where I: Iterator {} -#[must_use = "iterators are lazy and do nothing unless consumed"] -#[derive(Clone)] -struct ExactSize { - iter: I, - len: usize, +impl<'a, E: Event> ExactSizeIterator for ManualEventIterator<'a, E> { + fn len(&self) -> usize { + self.iter.len() + } } -impl ExactSize { - fn new(iter: I, len: usize) -> Self { - ExactSize { iter, len } + +impl<'a, E: Event> DoubleEndedIterator for ManualEventIterator<'a, E> { + fn next_back(&mut self) -> Option { + self.iter.next_back().map(|(event, _)| event) } } -impl Iterator for ExactSize { - type Item = I::Item; +#[derive(Debug)] +pub struct ManualEventIteratorWithId<'a, E: Event> { + reader: &'a mut ManualEventReader, + chain: Chain>, Iter<'a, EventInstance>>, + unread: usize, +} + +fn event_trace(id: EventId) { + trace!("EventReader::iter() -> {}", id); +} - #[inline] - fn next(&mut self) -> Option { - self.iter.next().map(|e| { - self.len -= 1; - e - }) +impl<'a, E: Event> ManualEventIteratorWithId<'a, E> { + pub fn new(reader: &'a mut ManualEventReader, events: &'a Events) -> Self { + let a_index = (reader.last_event_count).saturating_sub(events.events_a.start_event_count); + let b_index = (reader.last_event_count).saturating_sub(events.events_b.start_event_count); + let a = events.events_a.get(a_index..).unwrap_or_default(); + let b = events.events_b.get(b_index..).unwrap_or_default(); + + let unread_count = a.len() + b.len(); + // Ensure `len` is implemented correctly + debug_assert_eq!(unread_count, reader.len(events)); + reader.last_event_count = events.event_count - unread_count; + // Iterate the oldest first, then the newer events + let chain = a.iter().chain(b.iter()); + + Self { + reader, + chain, + unread: unread_count, + } + } + + /// Iterate over only the events. + pub fn without_id(self) -> ManualEventIterator<'a, E> { + ManualEventIterator { iter: self } + } +} + +impl<'a, E: Event> Iterator for ManualEventIteratorWithId<'a, E> { + type Item = (&'a E, EventId); + fn next(&mut self) -> Option { + match self + .chain + .next() + .map(|instance| (&instance.event, instance.event_id)) + { + Some(item) => { + event_trace(item.1); + self.reader.last_event_count += 1; + self.unread -= 1; + Some(item) + } + None => None, + } } - #[inline] fn size_hint(&self) -> (usize, Option) { - (self.len, Some(self.len)) + self.chain.size_hint() } } -impl DoubleEndedIterator for ExactSize { - #[inline] - fn next_back(&mut self) -> Option { - self.iter.next_back().map(|e| { - self.len -= 1; - e - }) +impl<'a, E: Event> DoubleEndedIterator for ManualEventIteratorWithId<'a, E> { + fn next_back(&mut self) -> Option { + match self + .chain + .next_back() + .map(|instance| (&instance.event, instance.event_id)) + { + Some(item) => { + event_trace(item.1); + self.unread -= 1; + Some(item) + } + None => None, + } } } -impl ExactSizeIterator for ExactSize { + +impl<'a, E: Event> ExactSizeIterator for ManualEventIteratorWithId<'a, E> { fn len(&self) -> usize { - self.len + self.unread } } @@ -548,6 +581,34 @@ impl Events { ) -> impl DoubleEndedIterator + ExactSizeIterator { self.events_b.iter().map(|i| &i.event) } + + /// Get a specific event by id if it still exists in the events buffer. + pub fn get_event(&self, id: usize) -> Option<(&E, EventId)> { + if id < self.oldest_id() { + return None; + } + + let sequence = self.sequence(id); + let index = id.saturating_sub(sequence.start_event_count); + + sequence + .get(index) + .map(|instance| (&instance.event, instance.event_id)) + } + + /// Oldest id still in the events buffer. + pub fn oldest_id(&self) -> usize { + self.events_a.start_event_count + } + + /// Which event buffer is this event id a part of. + fn sequence(&self, id: usize) -> &EventSequence { + if id < self.events_b.start_event_count { + &self.events_a + } else { + &self.events_b + } + } } impl std::iter::Extend for Events { diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index d4f79bec0f9ad1..46f1a8dc4281c6 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1551,9 +1551,7 @@ mod tests { world_a.flush(); let world_a_max_entities = world_a.entities().len(); - world_b - .entities - .reserve_entities(world_a_max_entities as u32); + world_b.entities.reserve_entities(world_a_max_entities); world_b.entities.flush_as_invalid(); let e4 = world_b.spawn(A(4)).id(); diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index a28e6df23ef837..ea18adf8410731 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -71,16 +71,21 @@ impl fmt::Debug for Access { } impl Default for Access { fn default() -> Self { + Self::new() + } +} + +impl Access { + /// Creates an empty [`Access`] collection. + pub const fn new() -> Self { Self { reads_all: false, - reads_and_writes: Default::default(), - writes: Default::default(), + reads_and_writes: FixedBitSet::new(), + writes: FixedBitSet::new(), marker: PhantomData, } } -} -impl Access { /// Increases the set capacity to the specified amount. /// /// Does nothing if `capacity` is less than or equal to the current value. diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 3844dcb74a9328..138235b836e194 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -151,7 +151,7 @@ where .archetypes .get(location.archetype_id) .debug_checked_unwrap(); - let table = self.tables.get(archetype.table_id()).debug_checked_unwrap(); + let table = self.tables.get(location.table_id).debug_checked_unwrap(); // SAFETY: `archetype` is from the world that `fetch/filter` were created for, // `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with @@ -170,12 +170,11 @@ where table, ); - let table_row = archetype.entity_table_row(location.archetype_row); // SAFETY: set_archetype was called prior. // `location.archetype_row` is an archetype index row in range of the current archetype, because if it was not, the match above would have `continue`d - if F::filter_fetch(&mut self.filter, entity, table_row) { + if F::filter_fetch(&mut self.filter, entity, location.table_row) { // SAFETY: set_archetype was called prior, `location.archetype_row` is an archetype index in range of the current archetype - return Some(Q::fetch(&mut self.fetch, entity, table_row)); + return Some(Q::fetch(&mut self.fetch, entity, location.table_row)); } } None diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index b0f263e7fd9480..c96dbca3938f83 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -408,17 +408,16 @@ impl QueryState { let mut fetch = Q::init_fetch(world, &self.fetch_state, last_change_tick, change_tick); let mut filter = F::init_fetch(world, &self.filter_state, last_change_tick, change_tick); - let table_row = archetype.entity_table_row(location.archetype_row); let table = world .storages() .tables - .get(archetype.table_id()) + .get(location.table_id) .debug_checked_unwrap(); Q::set_archetype(&mut fetch, &self.fetch_state, archetype, table); F::set_archetype(&mut filter, &self.filter_state, archetype, table); - if F::filter_fetch(&mut filter, entity, table_row) { - Ok(Q::fetch(&mut fetch, entity, table_row)) + if F::filter_fetch(&mut filter, entity, location.table_row) { + Ok(Q::fetch(&mut fetch, entity, location.table_row)) } else { Err(QueryEntityError::QueryDoesNotMatch(entity)) } diff --git a/crates/bevy_ecs/src/storage/table.rs b/crates/bevy_ecs/src/storage/table.rs index f14dbcedf42046..e5dc58dc26d5a7 100644 --- a/crates/bevy_ecs/src/storage/table.rs +++ b/crates/bevy_ecs/src/storage/table.rs @@ -12,18 +12,33 @@ use std::{ ops::{Index, IndexMut}, }; +/// An opaque unique ID for a [`Table`] within a [`World`]. +/// +/// Can be used with [`Tables::get`] to fetch the corresponding +/// table. +/// +/// Each [`Archetype`] always points to a table via [`Archetype::table_id`]. +/// Multiple archetypes can point to the same table so long as the components +/// stored in the table are identical, but do not share the same sparse set +/// components. +/// +/// [`World`]: crate::world::World +/// [`Archetype`]: crate::archetype::Archetype +/// [`Archetype::table_id`]: crate::archetype::Archetype::table_id #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct TableId(usize); +pub struct TableId(u32); impl TableId { + pub(crate) const INVALID: TableId = TableId(u32::MAX); + #[inline] pub fn new(index: usize) -> Self { - TableId(index) + TableId(index as u32) } #[inline] pub fn index(self) -> usize { - self.0 + self.0 as usize } #[inline] @@ -49,19 +64,21 @@ impl TableId { /// [`Archetype::table_id`]: crate::archetype::Archetype::table_id /// [`Entity`]: crate::entity::Entity #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct TableRow(usize); +pub struct TableRow(u32); impl TableRow { + pub const INVALID: TableRow = TableRow(u32::MAX); + /// Creates a `TableRow`. #[inline] pub const fn new(index: usize) -> Self { - Self(index) + Self(index as u32) } /// Gets the index of the row. #[inline] pub const fn index(self) -> usize { - self.0 + self.0 as usize } } @@ -568,7 +585,7 @@ impl Table { column.added_ticks.push(UnsafeCell::new(Tick::new(0))); column.changed_ticks.push(UnsafeCell::new(Tick::new(0))); } - TableRow(index) + TableRow::new(index) } #[inline] @@ -677,7 +694,7 @@ impl Tables { table.add_column(components.get_info_unchecked(*component_id)); } tables.push(table.build()); - (component_ids.to_vec(), TableId(tables.len() - 1)) + (component_ids.to_vec(), TableId::new(tables.len() - 1)) }); *value diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index e89d3659486c83..9f1c46a2e7b2b3 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -528,6 +528,85 @@ impl<'w, 's> Commands<'w, 's> { } } +/// A [`Command`] which gets executed for a given [`Entity`]. +/// +/// # Examples +/// +/// ``` +/// # use std::collections::HashSet; +/// # use bevy_ecs::prelude::*; +/// use bevy_ecs::system::EntityCommand; +/// # +/// # #[derive(Component, PartialEq)] +/// # struct Name(String); +/// # impl Name { +/// # fn new(s: String) -> Self { Name(s) } +/// # fn as_str(&self) -> &str { &self.0 } +/// # } +/// +/// #[derive(Resource, Default)] +/// struct Counter(i64); +/// +/// /// A `Command` which names an entity based on a global counter. +/// struct CountName; +/// +/// impl EntityCommand for CountName { +/// fn write(self, id: Entity, world: &mut World) { +/// // Get the current value of the counter, and increment it for next time. +/// let mut counter = world.resource_mut::(); +/// let i = counter.0; +/// counter.0 += 1; +/// +/// // Name the entity after the value of the counter. +/// world.entity_mut(id).insert(Name::new(format!("Entity #{i}"))); +/// } +/// } +/// +/// // App creation boilerplate omitted... +/// # let mut world = World::new(); +/// # world.init_resource::(); +/// # +/// # let mut setup_stage = SystemStage::single_threaded().with_system(setup); +/// # let mut assert_stage = SystemStage::single_threaded().with_system(assert_names); +/// # +/// # setup_stage.run(&mut world); +/// # assert_stage.run(&mut world); +/// +/// fn setup(mut commands: Commands) { +/// commands.spawn_empty().add(CountName); +/// commands.spawn_empty().add(CountName); +/// } +/// +/// fn assert_names(named: Query<&Name>) { +/// // We use a HashSet because we do not care about the order. +/// let names: HashSet<_> = named.iter().map(Name::as_str).collect(); +/// assert_eq!(names, HashSet::from_iter(["Entity #0", "Entity #1"])); +/// } +/// ``` +pub trait EntityCommand: Send + 'static { + fn write(self, id: Entity, world: &mut World); + /// Returns a [`Command`] which executes this [`EntityCommand`] for the given [`Entity`]. + fn with_entity(self, id: Entity) -> WithEntity + where + Self: Sized, + { + WithEntity { cmd: self, id } + } +} + +/// Turns an [`EntityCommand`] type into a [`Command`] type. +pub struct WithEntity { + cmd: C, + id: Entity, +} + +impl Command for WithEntity { + #[inline] + fn write(self, world: &mut World) { + self.cmd.write(self.id, world); + } +} + /// A list of commands that will be run to modify an [entity](crate::entity). pub struct EntityCommands<'w, 's, 'a> { entity: Entity, @@ -690,6 +769,27 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> { }); } + /// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`]. + /// + /// # Examples + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # fn my_system(mut commands: Commands) { + /// commands + /// .spawn_empty() + /// // Closures with this signature implement `EntityCommand`. + /// .add(|id: Entity, world: &mut World| { + /// println!("Executed an EntityCommand for {id:?}"); + /// }); + /// # } + /// # bevy_ecs::system::assert_is_system(my_system); + /// ``` + pub fn add(&mut self, command: C) -> &mut Self { + self.commands.add(command.with_entity(self.entity)); + self + } + /// Logs the components of the entity at the info level. /// /// # Panics @@ -709,13 +809,22 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> { impl Command for F where - F: FnOnce(&mut World) + Send + Sync + 'static, + F: FnOnce(&mut World) + Send + 'static, { fn write(self, world: &mut World) { self(world); } } +impl EntityCommand for F +where + F: FnOnce(Entity, &mut World) + Send + 'static, +{ + fn write(self, id: Entity, world: &mut World) { + self(id, world); + } +} + #[derive(Debug)] pub struct Spawn { pub bundle: T, diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 0dcc806c6d44ab..be66144b4a6a8a 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -41,9 +41,35 @@ //! //! - **System Stages:** They determine hard execution synchronization boundaries inside of //! which systems run in parallel by default. -//! - **Labeling:** First, systems are labeled upon creation by calling `.label()`. Then, -//! methods such as `.before()` and `.after()` are appended to systems to determine -//! execution order in respect to other systems. +//! - **Labels:** Systems may be ordered within a stage using the methods `.before()` and `.after()`, +//! which order systems based on their [`SystemLabel`]s. Each system is implicitly labeled with +//! its `fn` type, and custom labels may be added by calling `.label()`. +//! +//! [`SystemLabel`]: crate::schedule::SystemLabel +//! +//! ## Example +//! +//! ``` +//! # use bevy_ecs::prelude::*; +//! # let mut app = SystemStage::single_threaded(); +//! // Prints "Hello, World!" each frame. +//! app +//! .add_system(print_first.before(print_mid)) +//! .add_system(print_mid) +//! .add_system(print_last.after(print_mid)); +//! # let mut world = World::new(); +//! # app.run(&mut world); +//! +//! fn print_first() { +//! print!("Hello"); +//! } +//! fn print_mid() { +//! print!(", "); +//! } +//! fn print_last() { +//! println!("World!"); +//! } +//! ``` //! //! # System parameter list //! Following is the complete list of accepted types as system parameters: diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 63f356c4b592c5..8c9ff2ddfa01b9 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -239,6 +239,114 @@ fn assert_component_access_compatibility( query_type, filter_type, system_name, accesses); } +/// A collection of potentially conflicting [`SystemParam`]s allowed by disjoint access. +/// +/// Allows systems to safely access and interact with up to 8 mutually exclusive [`SystemParam`]s, such as +/// two queries that reference the same mutable data or an event reader and writer of the same type. +/// +/// Each individual [`SystemParam`] can be accessed by using the functions `p0()`, `p1()`, ..., `p7()`, +/// according to the order they are defined in the `ParamSet`. This ensures that there's either +/// only one mutable reference to a parameter at a time or any number of immutable references. +/// +/// # Examples +/// +/// The following system mutably accesses the same component two times, +/// which is not allowed due to rust's mutability rules. +/// +/// ```should_panic +/// # use bevy_ecs::prelude::*; +/// # +/// # #[derive(Component)] +/// # struct Health; +/// # +/// # #[derive(Component)] +/// # struct Enemy; +/// # +/// # #[derive(Component)] +/// # struct Ally; +/// # +/// // This will panic at runtime when the system gets initialized. +/// fn bad_system( +/// mut enemies: Query<&mut Health, With>, +/// mut allies: Query<&mut Health, With>, +/// ) { +/// // ... +/// } +/// # +/// # let mut bad_system_system = bevy_ecs::system::IntoSystem::into_system(bad_system); +/// # let mut world = World::new(); +/// # bad_system_system.initialize(&mut world); +/// # bad_system_system.run((), &mut world); +/// ``` +/// +/// Conflicing `SystemParam`s like these can be placed in a `ParamSet`, +/// which leverages the borrow checker to ensure that only one of the contained parameters are accessed at a given time. +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # +/// # #[derive(Component)] +/// # struct Health; +/// # +/// # #[derive(Component)] +/// # struct Enemy; +/// # +/// # #[derive(Component)] +/// # struct Ally; +/// # +/// // Given the following system +/// fn fancy_system( +/// mut set: ParamSet<( +/// Query<&mut Health, With>, +/// Query<&mut Health, With>, +/// )> +/// ) { +/// // This will access the first `SystemParam`. +/// for mut health in set.p0().iter_mut() { +/// // Do your fancy stuff here... +/// } +/// +/// // The second `SystemParam`. +/// // This would fail to compile if the previous parameter was still borrowed. +/// for mut health in set.p1().iter_mut() { +/// // Do even fancier stuff here... +/// } +/// } +/// # bevy_ecs::system::assert_is_system(fancy_system); +/// ``` +/// +/// Of course, `ParamSet`s can be used with any kind of `SystemParam`, not just [queries](Query). +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # +/// # struct MyEvent; +/// # impl MyEvent { +/// # pub fn new() -> Self { Self } +/// # } +/// fn event_system( +/// mut set: ParamSet<( +/// // `EventReader`s and `EventWriter`s conflict with each other, +/// // since they both access the event queue resource for `MyEvent`. +/// EventReader, +/// EventWriter, +/// // `&World` reads the entire world, so a `ParamSet` is the only way +/// // that it can be used in the same system as any mutable accesses. +/// &World, +/// )>, +/// ) { +/// for event in set.p0().iter() { +/// // ... +/// # let _event = event; +/// } +/// set.p1().send(MyEvent::new()); +/// +/// let entities = set.p2().entities(); +/// // ... +/// # let _entities = entities; +/// } +/// # bevy_ecs::system::assert_is_system(event_system); +/// ``` pub struct ParamSet<'w, 's, T: SystemParam> { param_states: &'s mut T::State, world: &'w World, @@ -1661,6 +1769,44 @@ mod tests { _local: Local<'s, T>, } + #[derive(Resource)] + pub struct R; + + #[derive(SystemParam)] + pub struct ConstGenericParam<'w, const I: usize>(Res<'w, R>); + + #[derive(SystemParam)] + pub struct LongParam<'w> { + _r0: Res<'w, R<0>>, + _r1: Res<'w, R<1>>, + _r2: Res<'w, R<2>>, + _r3: Res<'w, R<3>>, + _r4: Res<'w, R<4>>, + _r5: Res<'w, R<5>>, + _r6: Res<'w, R<6>>, + _r7: Res<'w, R<7>>, + _r8: Res<'w, R<8>>, + _r9: Res<'w, R<9>>, + _r10: Res<'w, R<10>>, + _r11: Res<'w, R<11>>, + _r12: Res<'w, R<12>>, + _r13: Res<'w, R<13>>, + _r14: Res<'w, R<14>>, + _r15: Res<'w, R<15>>, + _r16: Res<'w, R<16>>, + } + + #[allow(dead_code)] + fn long_system(_param: LongParam) { + crate::system::assert_is_system(long_system); + } + + #[derive(SystemParam)] + pub struct UnitParam; + #[derive(SystemParam)] - pub struct UnitParam {} + pub struct TupleParam<'w, 's, R: Resource, L: FromWorld + Send + 'static>( + Res<'w, R>, + Local<'s, L>, + ); } diff --git a/crates/bevy_ecs/src/system/system_piping.rs b/crates/bevy_ecs/src/system/system_piping.rs index d28158b697836d..dd343461b9e5fe 100644 --- a/crates/bevy_ecs/src/system/system_piping.rs +++ b/crates/bevy_ecs/src/system/system_piping.rs @@ -54,6 +54,21 @@ pub struct PipeSystem { archetype_component_access: Access, } +impl PipeSystem { + /// Manual constructor for creating a [`PipeSystem`]. + /// This should only be used when [`IntoPipeSystem::pipe`] cannot be used, + /// such as in `const` contexts. + pub const fn new(system_a: SystemA, system_b: SystemB, name: Cow<'static, str>) -> Self { + Self { + system_a, + system_b, + name, + component_access: Access::new(), + archetype_component_access: Access::new(), + } + } +} + impl> System for PipeSystem { type In = SystemA::In; type Out = SystemB::Out; @@ -154,13 +169,8 @@ where fn pipe(self, system: SystemB) -> PipeSystem { let system_a = IntoSystem::into_system(self); let system_b = IntoSystem::into_system(system); - PipeSystem { - name: Cow::Owned(format!("Pipe({}, {})", system_a.name(), system_b.name())), - system_a, - system_b, - archetype_component_access: Default::default(), - component_access: Default::default(), - } + let name = format!("Pipe({}, {})", system_a.name(), system_b.name()); + PipeSystem::new(system_a, system_b, Cow::Owned(name)) } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 458564beb4cfe3..251945d711b09c 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -7,7 +7,7 @@ use crate::{ TickCells, }, entity::{Entities, Entity, EntityLocation}, - storage::{Column, ComponentSparseSet, SparseSet, Storages, TableRow}, + storage::{Column, ComponentSparseSet, SparseSet, Storages}, world::{Mut, World}, }; use bevy_ptr::{OwningPtr, Ptr}; @@ -157,6 +157,9 @@ impl<'w> EntityRef<'w> { self.location, ) .map(|(value, ticks)| Mut { + // SAFETY: + // - returned component is of type T + // - Caller guarentees that this reference will not alias. value: value.assert_unique().deref_mut::(), ticks: Ticks::from_tick_cells(ticks, last_change_tick, change_tick), }) @@ -371,8 +374,7 @@ impl<'w> EntityMut<'w> { ); // SAFETY: location matches current entity. `T` matches `bundle_info` unsafe { - self.location = - bundle_inserter.insert(self.entity, self.location.archetype_row, bundle); + self.location = bundle_inserter.insert(self.entity, self.location, bundle); } self @@ -408,7 +410,6 @@ impl<'w> EntityMut<'w> { return None; } - let old_archetype = &mut archetypes[old_location.archetype_id]; let mut bundle_components = bundle_info.component_ids.iter().cloned(); let entity = self.entity; // SAFETY: bundle components are iterated in order, which guarantees that the component type @@ -420,7 +421,6 @@ impl<'w> EntityMut<'w> { take_component( components, storages, - old_archetype, removed_components, component_id, entity, @@ -702,12 +702,8 @@ fn fetch_table( world: &World, location: EntityLocation, component_id: ComponentId, -) -> Option<(&Column, TableRow)> { - let archetype = &world.archetypes[location.archetype_id]; - let table = &world.storages.tables[archetype.table_id()]; - let components = table.get_column(component_id)?; - let table_row = archetype.entity_table_row(location.archetype_row); - Some((components, table_row)) +) -> Option<&Column> { + world.storages.tables[location.table_id].get_column(component_id) } #[inline] @@ -719,8 +715,8 @@ fn fetch_sparse_set(world: &World, component_id: ComponentId) -> Option<&Compone /// Get a raw pointer to a particular [`Component`] on a particular [`Entity`] in the provided [`World`]. /// /// # Safety -/// - `entity_location` must be within bounds of the given archetype and `entity` must exist inside -/// the archetype +/// - `location` must be within bounds of the given archetype and table and `entity` must exist inside +/// the archetype and table /// - `component_id` must be valid /// - `storage_type` must accurately reflect where the components for `component_id` are stored. #[inline] @@ -733,9 +729,9 @@ pub(crate) unsafe fn get_component( ) -> Option> { match storage_type { StorageType::Table => { - let (components, table_row) = fetch_table(world, location, component_id)?; + let components = fetch_table(world, location, component_id)?; // SAFETY: archetypes only store valid table_rows and the stored component type is T - Some(components.get_data_unchecked(table_row)) + Some(components.get_data_unchecked(location.table_row)) } StorageType::SparseSet => fetch_sparse_set(world, component_id)?.get(entity), } @@ -745,9 +741,9 @@ pub(crate) unsafe fn get_component( /// Get a raw pointer to the [`ComponentTicks`] of a particular [`Component`] on a particular [`Entity`] in the provided [World]. /// /// # Safety -/// - `entity_location` must be within bounds of the given archetype and `entity` must exist inside +/// - Caller must ensure that `component_id` is valid +/// - `location` must be within bounds of the given archetype and `entity` must exist inside /// the archetype -/// - `component_id` must be valid /// - `storage_type` must accurately reflect where the components for `component_id` are stored. #[inline] unsafe fn get_component_and_ticks( @@ -759,13 +755,13 @@ unsafe fn get_component_and_ticks( ) -> Option<(Ptr<'_>, TickCells<'_>)> { match storage_type { StorageType::Table => { - let (components, table_row) = fetch_table(world, location, component_id)?; + let components = fetch_table(world, location, component_id)?; // SAFETY: archetypes only store valid table_rows and the stored component type is T Some(( - components.get_data_unchecked(table_row), + components.get_data_unchecked(location.table_row), TickCells { - added: components.get_added_ticks_unchecked(table_row), - changed: components.get_changed_ticks_unchecked(table_row), + added: components.get_added_ticks_unchecked(location.table_row), + changed: components.get_changed_ticks_unchecked(location.table_row), }, )) } @@ -787,9 +783,9 @@ unsafe fn get_ticks( ) -> Option { match storage_type { StorageType::Table => { - let (components, table_row) = fetch_table(world, location, component_id)?; + let components = fetch_table(world, location, component_id)?; // SAFETY: archetypes only store valid table_rows and the stored component type is T - Some(components.get_ticks_unchecked(table_row)) + Some(components.get_ticks_unchecked(location.table_row)) } StorageType::SparseSet => fetch_sparse_set(world, component_id)?.get_ticks(entity), } @@ -803,14 +799,14 @@ unsafe fn get_ticks( /// Caller is responsible to drop component data behind returned pointer. /// /// # Safety -/// - `entity_location` must be within bounds of the given archetype and `entity` must exist inside the archetype +/// - `location` must be within bounds of the given archetype and table and `entity` must exist inside the archetype +/// and table. /// - `component_id` must be valid /// - The relevant table row **must be removed** by the caller once all components are taken #[inline] unsafe fn take_component<'a>( components: &Components, storages: &'a mut Storages, - archetype: &Archetype, removed_components: &mut SparseSet>, component_id: ComponentId, entity: Entity, @@ -821,12 +817,13 @@ unsafe fn take_component<'a>( removed_components.push(entity); match component_info.storage_type() { StorageType::Table => { - let table = &mut storages.tables[archetype.table_id()]; + let table = &mut storages.tables[location.table_id]; // SAFETY: archetypes will always point to valid columns let components = table.get_column_mut(component_id).unwrap(); - let table_row = archetype.entity_table_row(location.archetype_row); // SAFETY: archetypes only store valid table_rows and the stored component type is T - components.get_data_unchecked_mut(table_row).promote() + components + .get_data_unchecked_mut(location.table_row) + .promote() } StorageType::SparseSet => storages .sparse_sets diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 9e4b5757214367..e4569f6be52206 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -15,6 +15,7 @@ use crate::{ Component, ComponentDescriptor, ComponentId, ComponentInfo, Components, TickCells, }, entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation}, + event::{Event, Events}, query::{QueryState, ReadOnlyWorldQuery, WorldQuery}, storage::{ResourceData, SparseSet, Storages}, system::Resource, @@ -333,6 +334,8 @@ impl World { let location = EntityLocation { archetype_id: archetype.id(), archetype_row: ArchetypeRow::new(archetype_row), + table_id: archetype.table_id(), + table_row: archetype_entity.table_row(), }; EntityRef::new(self, archetype_entity.entity(), location) }) @@ -1134,7 +1137,7 @@ impl World { if location.archetype_id == archetype => { // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter - unsafe { inserter.insert(entity, location.archetype_row, bundle) }; + unsafe { inserter.insert(entity, location, bundle) }; } _ => { let mut inserter = bundle_info.get_bundle_inserter( @@ -1146,7 +1149,7 @@ impl World { change_tick, ); // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter - unsafe { inserter.insert(entity, location.archetype_row, bundle) }; + unsafe { inserter.insert(entity, location, bundle) }; spawn_or_insert = SpawnOrInsert::Insert(inserter, location.archetype_id); } @@ -1268,22 +1271,22 @@ impl World { result } - /// Sends an [`Event`](crate::event::Event). + /// Sends an [`Event`]. #[inline] - pub fn send_event(&mut self, event: E) { + pub fn send_event(&mut self, event: E) { self.send_event_batch(std::iter::once(event)); } - /// Sends the default value of the [`Event`](crate::event::Event) of type `E`. + /// Sends the default value of the [`Event`] of type `E`. #[inline] - pub fn send_event_default(&mut self) { + pub fn send_event_default(&mut self) { self.send_event_batch(std::iter::once(E::default())); } - /// Sends a batch of [`Event`](crate::event::Event)s from an iterator. + /// Sends a batch of [`Event`]s from an iterator. #[inline] - pub fn send_event_batch(&mut self, events: impl Iterator) { - match self.get_resource_mut::>() { + pub fn send_event_batch(&mut self, events: impl IntoIterator) { + match self.get_resource_mut::>() { Some(mut events_resource) => events_resource.extend(events), None => bevy_utils::tracing::error!( "Unable to send event `{}`\n\tEvent must be added to the app with `add_event()`\n\thttps://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event ", diff --git a/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_iter_many_mut_lifetime_safety.stderr b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_iter_many_mut_lifetime_safety.stderr index a78f5a85ec8493..f2aa5dc1a55666 100644 --- a/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_iter_many_mut_lifetime_safety.stderr +++ b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_iter_many_mut_lifetime_safety.stderr @@ -1,5 +1,8 @@ error[E0499]: cannot borrow `iter` as mutable more than once at a time - --> tests/ui/system_query_iter_many_mut_lifetime_safety.rs:9:25 - | -9 | while let Some(a) = iter.fetch_next() { - | ^^^^^^^^^^^^^^^^^ `iter` was mutably borrowed here in the previous iteration of the loop + --> tests/ui/system_query_iter_many_mut_lifetime_safety.rs:9:25 + | +9 | while let Some(a) = iter.fetch_next() { + | ^^^^^^^^^^^^^^^^^ `iter` was mutably borrowed here in the previous iteration of the loop +10 | // this should fail to compile +11 | results.push(a); + | --------------- first borrow used here, in later iteration of loop diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 79d19e2898ef49..81d793993ac7c5 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -465,7 +465,7 @@ async fn load_gltf<'a, 'b>( let mut entity_to_skin_index_map = HashMap::new(); world - .spawn(SpatialBundle::VISIBLE_IDENTITY) + .spawn(SpatialBundle::INHERITED_IDENTITY) .with_children(|parent| { for node in scene.nodes() { let result = load_node( diff --git a/crates/bevy_hierarchy/src/child_builder.rs b/crates/bevy_hierarchy/src/child_builder.rs index fb092cee0b241f..9e2c328348d040 100644 --- a/crates/bevy_hierarchy/src/child_builder.rs +++ b/crates/bevy_hierarchy/src/child_builder.rs @@ -2,17 +2,17 @@ use crate::{Children, HierarchyEvent, Parent}; use bevy_ecs::{ bundle::Bundle, entity::Entity, - event::Events, + prelude::Events, system::{Command, Commands, EntityCommands}, world::{EntityMut, World}, }; use smallvec::SmallVec; -fn push_events(world: &mut World, events: SmallVec<[HierarchyEvent; 8]>) { +// Do not use `world.send_event_batch` as it prints error message when the Events are not available in the world, +// even though it's a valid use case to execute commands on a world without events. Loading a GLTF file for example +fn push_events(world: &mut World, events: impl IntoIterator) { if let Some(mut moved) = world.get_resource_mut::>() { - for evt in events { - moved.send(evt); - } + moved.extend(events); } } @@ -37,6 +37,9 @@ fn update_parent(world: &mut World, child: Entity, new_parent: Entity) -> Option } } +/// Remove child from the parent's [`Children`] component. +/// +/// Removes the [`Children`] component from the parent if it's empty. fn remove_from_children(world: &mut World, parent: Entity, child: Entity) { let mut parent = world.entity_mut(parent); if let Some(mut children) = parent.get_mut::() { @@ -47,24 +50,64 @@ fn remove_from_children(world: &mut World, parent: Entity, child: Entity) { } } +/// Update the [`Parent`] component of the `child`. +/// Removes the `child` from the previous parent's [`Children`]. +/// +/// Does not update the new parents [`Children`] component. +/// +/// Does nothing if `child` was already a child of `parent`. +/// +/// Sends [`HierarchyEvent`]'s. +fn update_old_parent(world: &mut World, child: Entity, parent: Entity) { + let previous = update_parent(world, child, parent); + if let Some(previous_parent) = previous { + // Do nothing if the child was already parented to this entity. + if previous_parent == parent { + return; + } + remove_from_children(world, previous_parent, child); + + push_events( + world, + [HierarchyEvent::ChildMoved { + child, + previous_parent, + new_parent: parent, + }], + ); + } else { + push_events(world, [HierarchyEvent::ChildAdded { child, parent }]); + } +} + +/// Update the [`Parent`] components of the `children`. +/// Removes the `children` from their previous parent's [`Children`]. +/// +/// Does not update the new parents [`Children`] component. +/// +/// Does nothing for a child if it was already a child of `parent`. +/// +/// Sends [`HierarchyEvent`]'s. fn update_old_parents(world: &mut World, parent: Entity, children: &[Entity]) { - let mut moved: SmallVec<[HierarchyEvent; 8]> = SmallVec::with_capacity(children.len()); - for child in children { - if let Some(previous) = update_parent(world, *child, parent) { + let mut events: SmallVec<[HierarchyEvent; 8]> = SmallVec::with_capacity(children.len()); + for &child in children { + if let Some(previous) = update_parent(world, child, parent) { // Do nothing if the entity already has the correct parent. if parent == previous { continue; } - remove_from_children(world, previous, *child); - moved.push(HierarchyEvent::ChildMoved { - child: *child, + remove_from_children(world, previous, child); + events.push(HierarchyEvent::ChildMoved { + child, previous_parent: previous, new_parent: parent, }); + } else { + events.push(HierarchyEvent::ChildAdded { child, parent }); } } - push_events(world, moved); + push_events(world, events); } fn remove_children(parent: Entity, children: &[Entity], world: &mut World) { @@ -108,33 +151,7 @@ pub struct AddChild { impl Command for AddChild { fn write(self, world: &mut World) { - let previous = update_parent(world, self.child, self.parent); - if let Some(previous) = previous { - if previous == self.parent { - return; - } - remove_from_children(world, previous, self.child); - if let Some(mut events) = world.get_resource_mut::>() { - events.send(HierarchyEvent::ChildMoved { - child: self.child, - previous_parent: previous, - new_parent: self.parent, - }); - } - } else if let Some(mut events) = world.get_resource_mut::>() { - events.send(HierarchyEvent::ChildAdded { - child: self.child, - parent: self.parent, - }); - } - let mut parent = world.entity_mut(self.parent); - if let Some(mut children) = parent.get_mut::() { - if !children.contains(&self.child) { - children.0.push(self.child); - } - } else { - parent.insert(Children(smallvec::smallvec![self.child])); - } + world.entity_mut(self.parent).add_child(self.child); } } @@ -148,14 +165,9 @@ pub struct InsertChildren { impl Command for InsertChildren { fn write(self, world: &mut World) { - update_old_parents(world, self.parent, &self.children); - let mut parent = world.entity_mut(self.parent); - if let Some(mut children) = parent.get_mut::() { - children.0.retain(|value| !self.children.contains(value)); - children.0.insert_from_slice(self.index, &self.children); - } else { - parent.insert(Children(self.children)); - } + world + .entity_mut(self.parent) + .insert_children(self.index, &self.children); } } @@ -167,15 +179,8 @@ pub struct PushChildren { } impl Command for PushChildren { - fn write(mut self, world: &mut World) { - update_old_parents(world, self.parent, &self.children); - let mut parent = world.entity_mut(self.parent); - if let Some(mut children) = parent.get_mut::() { - children.0.retain(|child| !self.children.contains(child)); - children.0.append(&mut self.children); - } else { - parent.insert(Children(self.children)); - } + fn write(self, world: &mut World) { + world.entity_mut(self.parent).push_children(&self.children); } } @@ -198,17 +203,7 @@ pub struct RemoveParent { impl Command for RemoveParent { fn write(self, world: &mut World) { - if let Some(parent) = world.get::(self.child) { - let parent_entity = parent.get(); - remove_from_children(world, parent_entity, self.child); - world.entity_mut(self.child).remove::(); - if let Some(mut events) = world.get_resource_mut::>() { - events.send(HierarchyEvent::ChildRemoved { - child: self.child, - parent: parent_entity, - }); - } - } + world.entity_mut(self.child).remove_parent(); } } @@ -248,40 +243,7 @@ impl<'w, 's, 'a> ChildBuilder<'w, 's, 'a> { /// Trait defining how to build children pub trait BuildChildren { /// Creates a [`ChildBuilder`] with the given children built in the given closure - /// - /// Compared to [`add_children`][BuildChildren::add_children], this method returns self - /// to allow chaining. fn with_children(&mut self, f: impl FnOnce(&mut ChildBuilder)) -> &mut Self; - /// Creates a [`ChildBuilder`] with the given children built in the given closure - /// - /// Compared to [`with_children`][BuildChildren::with_children], this method returns the - /// the value returned from the closure, but doesn't allow chaining. - /// - /// ## Example - /// - /// ```no_run - /// # use bevy_ecs::prelude::*; - /// # use bevy_hierarchy::*; - /// # - /// # #[derive(Component)] - /// # struct SomethingElse; - /// # - /// # #[derive(Component)] - /// # struct MoreStuff; - /// # - /// # fn foo(mut commands: Commands) { - /// let mut parent_commands = commands.spawn_empty(); - /// let child_id = parent_commands.add_children(|parent| { - /// parent.spawn_empty().id() - /// }); - /// - /// parent_commands.insert(SomethingElse); - /// commands.entity(child_id).with_children(|parent| { - /// parent.spawn(MoreStuff); - /// }); - /// # } - /// ``` - fn add_children(&mut self, f: impl FnOnce(&mut ChildBuilder) -> T) -> T; /// Pushes children to the back of the builder's children. For any entities that are /// already a child of this one, this method does nothing. /// @@ -313,11 +275,6 @@ pub trait BuildChildren { impl<'w, 's, 'a> BuildChildren for EntityCommands<'w, 's, 'a> { fn with_children(&mut self, spawn_children: impl FnOnce(&mut ChildBuilder)) -> &mut Self { - self.add_children(spawn_children); - self - } - - fn add_children(&mut self, spawn_children: impl FnOnce(&mut ChildBuilder) -> T) -> T { let parent = self.id(); let mut builder = ChildBuilder { commands: self.commands(), @@ -327,11 +284,10 @@ impl<'w, 's, 'a> BuildChildren for EntityCommands<'w, 's, 'a> { }, }; - let result = spawn_children(&mut builder); + spawn_children(&mut builder); let children = builder.push_children; self.commands().add(children); - - result + self } fn push_children(&mut self, children: &[Entity]) -> &mut Self { @@ -393,12 +349,13 @@ impl<'w> WorldChildBuilder<'w> { pub fn spawn(&mut self, bundle: impl Bundle + Send + Sync + 'static) -> EntityMut<'_> { let entity = self.world.spawn((bundle, Parent(self.parent))).id(); push_child_unchecked(self.world, self.parent, entity); - if let Some(mut added) = self.world.get_resource_mut::>() { - added.send(HierarchyEvent::ChildAdded { + push_events( + self.world, + [HierarchyEvent::ChildAdded { child: entity, parent: self.parent, - }); - } + }], + ); self.world.entity_mut(entity) } @@ -406,12 +363,13 @@ impl<'w> WorldChildBuilder<'w> { pub fn spawn_empty(&mut self) -> EntityMut<'_> { let entity = self.world.spawn(Parent(self.parent)).id(); push_child_unchecked(self.world, self.parent, entity); - if let Some(mut added) = self.world.get_resource_mut::>() { - added.send(HierarchyEvent::ChildAdded { + push_events( + self.world, + [HierarchyEvent::ChildAdded { child: entity, parent: self.parent, - }); - } + }], + ); self.world.entity_mut(entity) } @@ -425,12 +383,28 @@ impl<'w> WorldChildBuilder<'w> { pub trait BuildWorldChildren { /// Creates a [`WorldChildBuilder`] with the given children built in the given closure fn with_children(&mut self, spawn_children: impl FnOnce(&mut WorldChildBuilder)) -> &mut Self; + + /// Adds a single child + /// + /// If the children were previously children of another parent, that parent's [`Children`] component + /// will have those children removed from its list. Removing all children from a parent causes its + /// [`Children`] component to be removed from the entity. + fn add_child(&mut self, child: Entity) -> &mut Self; + /// Pushes children to the back of the builder's children fn push_children(&mut self, children: &[Entity]) -> &mut Self; /// Inserts children at the given index fn insert_children(&mut self, index: usize, children: &[Entity]) -> &mut Self; /// Removes the given children fn remove_children(&mut self, children: &[Entity]) -> &mut Self; + + /// Set the `parent` of this entity. This entity will be added to the end of the `parent`'s list of children. + /// + /// If this entity already had a parent it will be removed from it. + fn set_parent(&mut self, parent: Entity) -> &mut Self; + + /// Remove the parent from this entity. + fn remove_parent(&mut self) -> &mut Self; } impl<'w> BuildWorldChildren for EntityMut<'w> { @@ -442,6 +416,20 @@ impl<'w> BuildWorldChildren for EntityMut<'w> { self } + fn add_child(&mut self, child: Entity) -> &mut Self { + let parent = self.id(); + self.world_scope(|world| { + update_old_parent(world, child, parent); + }); + if let Some(mut children_component) = self.get_mut::() { + children_component.0.retain(|value| child != *value); + children_component.0.push(child); + } else { + self.insert(Children::from_entities(&[child])); + } + self + } + fn push_children(&mut self, children: &[Entity]) -> &mut Self { let parent = self.id(); self.world_scope(|world| { @@ -481,21 +469,172 @@ impl<'w> BuildWorldChildren for EntityMut<'w> { }); self } + + fn set_parent(&mut self, parent: Entity) -> &mut Self { + let child = self.id(); + self.world_scope(|world| { + world.entity_mut(parent).add_child(child); + }); + self + } + + fn remove_parent(&mut self) -> &mut Self { + let child = self.id(); + if let Some(parent) = self.remove::().map(|p| p.get()) { + self.world_scope(|world| { + remove_from_children(world, parent, child); + push_events(world, [HierarchyEvent::ChildRemoved { child, parent }]); + }); + } + self + } } #[cfg(test)] mod tests { use super::{BuildChildren, BuildWorldChildren}; - use crate::prelude::{Children, Parent}; + use crate::{ + components::{Children, Parent}, + HierarchyEvent::{self, ChildAdded, ChildMoved, ChildRemoved}, + }; use smallvec::{smallvec, SmallVec}; use bevy_ecs::{ component::Component, entity::Entity, + event::Events, system::{CommandQueue, Commands}, world::World, }; + /// Assert the (non)existence and state of the child's [`Parent`] component. + fn assert_parent(world: &mut World, child: Entity, parent: Option) { + assert_eq!(world.get::(child).map(|p| p.get()), parent); + } + + /// Assert the (non)existence and state of the parent's [`Children`] component. + fn assert_children(world: &mut World, parent: Entity, children: Option<&[Entity]>) { + assert_eq!(world.get::(parent).map(|c| &**c), children); + } + + /// Used to omit a number of events that are not relevant to a particular test. + fn omit_events(world: &mut World, number: usize) { + let mut events_resource = world.resource_mut::>(); + let mut events: Vec<_> = events_resource.drain().collect(); + events_resource.extend(events.drain(number..)); + } + + fn assert_events(world: &mut World, expected_events: &[HierarchyEvent]) { + let events: Vec<_> = world + .resource_mut::>() + .drain() + .collect(); + assert_eq!(events, expected_events); + } + + #[test] + fn add_child() { + let world = &mut World::new(); + world.insert_resource(Events::::default()); + + let [a, b, c, d] = std::array::from_fn(|_| world.spawn_empty().id()); + + world.entity_mut(a).add_child(b); + + assert_parent(world, b, Some(a)); + assert_children(world, a, Some(&[b])); + assert_events( + world, + &[ChildAdded { + child: b, + parent: a, + }], + ); + + world.entity_mut(a).add_child(c); + + assert_children(world, a, Some(&[b, c])); + assert_parent(world, c, Some(a)); + assert_events( + world, + &[ChildAdded { + child: c, + parent: a, + }], + ); + // Children component should be removed when it's empty. + world.entity_mut(d).add_child(b).add_child(c); + assert_children(world, a, None); + } + + #[test] + fn set_parent() { + let world = &mut World::new(); + world.insert_resource(Events::::default()); + + let [a, b, c] = std::array::from_fn(|_| world.spawn_empty().id()); + + world.entity_mut(a).set_parent(b); + + assert_parent(world, a, Some(b)); + assert_children(world, b, Some(&[a])); + assert_events( + world, + &[ChildAdded { + child: a, + parent: b, + }], + ); + + world.entity_mut(a).set_parent(c); + + assert_parent(world, a, Some(c)); + assert_children(world, b, None); + assert_children(world, c, Some(&[a])); + assert_events( + world, + &[ChildMoved { + child: a, + previous_parent: b, + new_parent: c, + }], + ); + } + + #[test] + fn remove_parent() { + let world = &mut World::new(); + world.insert_resource(Events::::default()); + + let [a, b, c] = std::array::from_fn(|_| world.spawn_empty().id()); + + world.entity_mut(a).push_children(&[b, c]); + world.entity_mut(b).remove_parent(); + + assert_parent(world, b, None); + assert_parent(world, c, Some(a)); + assert_children(world, a, Some(&[c])); + omit_events(world, 2); // Omit ChildAdded events. + assert_events( + world, + &[ChildRemoved { + child: b, + parent: a, + }], + ); + + world.entity_mut(c).remove_parent(); + assert_parent(world, c, None); + assert_children(world, a, None); + assert_events( + world, + &[ChildRemoved { + child: c, + parent: a, + }], + ); + } + #[derive(Component)] struct C(u32); @@ -506,12 +645,13 @@ mod tests { let mut commands = Commands::new(&mut queue, &world); let parent = commands.spawn(C(1)).id(); - let children = commands.entity(parent).add_children(|parent| { - [ + let mut children = Vec::new(); + commands.entity(parent).with_children(|parent| { + children.extend([ parent.spawn(C(2)).id(), parent.spawn(C(3)).id(), parent.spawn(C(4)).id(), - ] + ]); }); queue.apply(&mut world); diff --git a/crates/bevy_hierarchy/src/events.rs b/crates/bevy_hierarchy/src/events.rs index 64f1d2118da846..f68c2a0788a7db 100644 --- a/crates/bevy_hierarchy/src/events.rs +++ b/crates/bevy_hierarchy/src/events.rs @@ -3,7 +3,7 @@ use bevy_ecs::prelude::Entity; /// An [`Event`] that is fired whenever there is a change in the world's hierarchy. /// /// [`Event`]: bevy_ecs::event::Event -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum HierarchyEvent { /// Fired whenever an [`Entity`] is added as a child to a parent. ChildAdded { diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index 90482b3e3615ba..a1e42d64a12fe3 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -138,7 +138,7 @@ pub mod render { //! Cameras, meshes, textures, shaders, and pipelines. //! Use [`RenderDevice::features`](crate::render::renderer::RenderDevice::features), //! [`RenderDevice::limits`](crate::render::renderer::RenderDevice::limits), and the - //! [`WgpuAdapterInfo`](crate::render::render_resource::WgpuAdapterInfo) resource to + //! [`RenderAdapterInfo`](crate::render::renderer::RenderAdapterInfo) resource to //! get runtime information about the actual adapter, backend, features, and limits. pub use bevy_render::*; } diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index 21079580f925ff..7265f4fd621707 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -154,10 +154,14 @@ impl Plugin for LogPlugin { let tracy_layer = tracing_tracy::TracyLayer::new(); let fmt_layer = tracing_subscriber::fmt::Layer::default(); + + // bevy_render::renderer logs a `tracy.frame_mark` event every frame + // at Level::INFO. Formatted logs should omit it. #[cfg(feature = "tracing-tracy")] - let fmt_layer = fmt_layer.with_filter( - tracing_subscriber::filter::Targets::new().with_target("tracy", Level::ERROR), - ); + let fmt_layer = + fmt_layer.with_filter(tracing_subscriber::filter::FilterFn::new(|meta| { + meta.fields().field("tracy.frame_mark").is_none() + })); let subscriber = subscriber.with(fmt_layer); diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index c1d0fc8297325b..d10fac03a684ea 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -10,12 +10,11 @@ use bevy_core_pipeline::{ }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - entity::Entity, event::EventReader, prelude::World, schedule::IntoSystemDescriptor, system::{ - lifetimeless::{Read, SQuery, SRes}, + lifetimeless::{Read, SRes}, Commands, Local, Query, Res, ResMut, Resource, SystemParamItem, }, world::FromWorld, @@ -27,8 +26,8 @@ use bevy_render::{ prelude::Image, render_asset::{PrepareAssetLabel, RenderAssets}, render_phase::{ - AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase, - SetItemPipeline, TrackedRenderPass, + AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, + RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::{ AsBindGroup, AsBindGroupError, BindGroup, BindGroupLayout, OwnedBindingResource, @@ -296,15 +295,19 @@ type DrawMaterial = ( /// Sets the bind group for a given [`Material`] at the configured `I` index. pub struct SetMaterialBindGroup(PhantomData); -impl EntityRenderCommand for SetMaterialBindGroup { - type Param = (SRes>, SQuery>>); +impl RenderCommand

for SetMaterialBindGroup { + type Param = SRes>; + type ViewWorldQuery = (); + type ItemWorldQuery = Read>; + + #[inline] fn render<'w>( - _view: Entity, - item: Entity, - (materials, query): SystemParamItem<'w, '_, Self::Param>, + _item: &P, + _view: (), + material_handle: &'_ Handle, + materials: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let material_handle = query.get(item).unwrap(); let material = materials.into_inner().get(material_handle).unwrap(); pass.set_bind_group(I, &material.bind_group, &[]); RenderCommandResult::Success diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index e23c1c811c4d7a..9cd2bf0bbd83c9 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -18,9 +18,8 @@ use bevy_render::{ render_asset::RenderAssets, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, render_phase::{ - CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, EntityPhaseItem, - EntityRenderCommand, PhaseItem, RenderCommandResult, RenderPhase, SetItemPipeline, - TrackedRenderPass, + CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, RenderCommand, + RenderCommandResult, RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::*, renderer::{RenderContext, RenderDevice, RenderQueue}, @@ -1699,6 +1698,11 @@ pub struct Shadow { impl PhaseItem for Shadow { type SortKey = FloatOrd; + #[inline] + fn entity(&self) -> Entity { + self.entity + } + #[inline] fn sort_key(&self) -> Self::SortKey { FloatOrd(self.distance) @@ -1715,12 +1719,6 @@ impl PhaseItem for Shadow { } } -impl EntityPhaseItem for Shadow { - fn entity(&self) -> Entity { - self.entity - } -} - impl CachedRenderPipelinePhaseItem for Shadow { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { @@ -1785,16 +1783,12 @@ impl Node for ShadowPassNode { }), }; - let draw_functions = world.resource::>(); let render_pass = render_context .command_encoder .begin_render_pass(&pass_descriptor); - let mut draw_functions = draw_functions.write(); - let mut tracked_pass = TrackedRenderPass::new(render_pass); - for item in &shadow_phase.items { - let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); - draw_function.draw(world, &mut tracked_pass, view_light_entity, item); - } + let mut render_pass = TrackedRenderPass::new(render_pass); + + shadow_phase.render(&mut render_pass, world, view_light_entity); } } @@ -1810,16 +1804,19 @@ pub type DrawShadowMesh = ( ); pub struct SetShadowViewBindGroup; -impl EntityRenderCommand for SetShadowViewBindGroup { - type Param = (SRes, SQuery>); +impl RenderCommand for SetShadowViewBindGroup { + type Param = SRes; + type ViewWorldQuery = Read; + type ItemWorldQuery = (); + #[inline] fn render<'w>( - view: Entity, - _item: Entity, - (light_meta, view_query): SystemParamItem<'w, '_, Self::Param>, + _item: &Shadow, + view_uniform_offset: &'_ ViewUniformOffset, + _entity: (), + light_meta: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let view_uniform_offset = view_query.get(view).unwrap(); pass.set_bind_group( I, light_meta diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 1c88cc99883bb8..92b65621922ac5 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -7,6 +7,7 @@ use bevy_app::Plugin; use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped}; use bevy_ecs::{ prelude::*, + query::ROQueryItem, system::{lifetimeless::*, SystemParamItem, SystemState}, }; use bevy_math::{Mat3A, Mat4, Vec2}; @@ -19,7 +20,7 @@ use bevy_render::{ GpuBufferInfo, Mesh, MeshVertexBufferLayout, }, render_asset::RenderAssets, - render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass}, + render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::{ @@ -172,11 +173,6 @@ pub fn extract_meshes( commands.insert_or_spawn_batch(not_caster_commands); } -#[derive(Resource, Debug, Default)] -pub struct ExtractedJoints { - pub buffer: Vec, -} - #[derive(Component)] pub struct SkinnedMeshJoints { pub index: u32, @@ -188,19 +184,22 @@ impl SkinnedMeshJoints { skin: &SkinnedMesh, inverse_bindposes: &Assets, joints: &Query<&GlobalTransform>, - buffer: &mut Vec, + buffer: &mut BufferVec, ) -> Option { let inverse_bindposes = inverse_bindposes.get(&skin.inverse_bindposes)?; - let bindposes = inverse_bindposes.iter(); - let skin_joints = skin.joints.iter(); let start = buffer.len(); - for (inverse_bindpose, joint) in bindposes.zip(skin_joints).take(MAX_JOINTS) { - if let Ok(joint) = joints.get(*joint) { - buffer.push(joint.affine() * *inverse_bindpose); - } else { - buffer.truncate(start); - return None; - } + let target = start + skin.joints.len().min(MAX_JOINTS); + buffer.extend( + joints + .iter_many(&skin.joints) + .zip(inverse_bindposes.iter()) + .map(|(joint, bindpose)| joint.affine() * *bindpose), + ); + // iter_many will skip any failed fetches. This will cause it to assign the wrong bones, + // so just bail by truncating to the start. + if buffer.len() != target { + buffer.truncate(start); + return None; } // Pad to 256 byte alignment @@ -221,13 +220,13 @@ impl SkinnedMeshJoints { pub fn extract_skinned_meshes( mut commands: Commands, mut previous_len: Local, - mut previous_joint_len: Local, + mut uniform: ResMut, query: Extract>, inverse_bindposes: Extract>>, joint_query: Extract>, ) { + uniform.buffer.clear(); let mut values = Vec::with_capacity(*previous_len); - let mut joints = Vec::with_capacity(*previous_joint_len); let mut last_start = 0; for (entity, computed_visibility, skin) in &query { @@ -236,7 +235,7 @@ pub fn extract_skinned_meshes( } // PERF: This can be expensive, can we move this to prepare? if let Some(skinned_joints) = - SkinnedMeshJoints::build(skin, &inverse_bindposes, &joint_query, &mut joints) + SkinnedMeshJoints::build(skin, &inverse_bindposes, &joint_query, &mut uniform.buffer) { last_start = last_start.max(skinned_joints.index as usize); values.push((entity, skinned_joints.to_buffer_index())); @@ -244,13 +243,11 @@ pub fn extract_skinned_meshes( } // Pad out the buffer to ensure that there's enough space for bindings - while joints.len() - last_start < MAX_JOINTS { - joints.push(Mat4::ZERO); + while uniform.buffer.len() - last_start < MAX_JOINTS { + uniform.buffer.push(Mat4::ZERO); } *previous_len = values.len(); - *previous_joint_len = joints.len(); - commands.insert_resource(ExtractedJoints { buffer: joints }); commands.insert_or_spawn_batch(values); } @@ -779,20 +776,14 @@ impl Default for SkinnedMeshUniform { pub fn prepare_skinned_meshes( render_device: Res, render_queue: Res, - extracted_joints: Res, mut skinned_mesh_uniform: ResMut, ) { - if extracted_joints.buffer.is_empty() { + if skinned_mesh_uniform.buffer.is_empty() { return; } - skinned_mesh_uniform.buffer.clear(); - skinned_mesh_uniform - .buffer - .reserve(extracted_joints.buffer.len(), &render_device); - for joint in &extracted_joints.buffer { - skinned_mesh_uniform.buffer.push(*joint); - } + let len = skinned_mesh_uniform.buffer.len(); + skinned_mesh_uniform.buffer.reserve(len, &render_device); skinned_mesh_uniform .buffer .write_buffer(&render_device, &render_queue); @@ -883,20 +874,23 @@ pub fn queue_mesh_view_bind_groups( } pub struct SetMeshViewBindGroup; -impl EntityRenderCommand for SetMeshViewBindGroup { - type Param = SQuery<( +impl RenderCommand

for SetMeshViewBindGroup { + type Param = (); + type ViewWorldQuery = ( Read, Read, Read, - )>; + ); + type ItemWorldQuery = (); + #[inline] fn render<'w>( - view: Entity, - _item: Entity, - view_query: SystemParamItem<'w, '_, Self::Param>, + _item: &P, + (view_uniform, view_lights, mesh_view_bind_group): ROQueryItem<'w, Self::ViewWorldQuery>, + _entity: (), + _: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let (view_uniform, view_lights, mesh_view_bind_group) = view_query.get_inner(view).unwrap(); pass.set_bind_group( I, &mesh_view_bind_group.value, @@ -908,22 +902,21 @@ impl EntityRenderCommand for SetMeshViewBindGroup { } pub struct SetMeshBindGroup; -impl EntityRenderCommand for SetMeshBindGroup { - type Param = ( - SRes, - SQuery<( - Read>, - Option>, - )>, +impl RenderCommand

for SetMeshBindGroup { + type Param = SRes; + type ViewWorldQuery = (); + type ItemWorldQuery = ( + Read>, + Option>, ); #[inline] fn render<'w>( - _view: Entity, - item: Entity, - (mesh_bind_group, mesh_query): SystemParamItem<'w, '_, Self::Param>, + _item: &P, + _view: (), + (mesh_index, skinned_mesh_joints): ROQueryItem<'_, Self::ItemWorldQuery>, + mesh_bind_group: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let (mesh_index, skinned_mesh_joints) = mesh_query.get(item).unwrap(); if let Some(joints) = skinned_mesh_joints { pass.set_bind_group( I, @@ -942,16 +935,18 @@ impl EntityRenderCommand for SetMeshBindGroup { } pub struct DrawMesh; -impl EntityRenderCommand for DrawMesh { - type Param = (SRes>, SQuery>>); +impl RenderCommand

for DrawMesh { + type Param = SRes>; + type ViewWorldQuery = (); + type ItemWorldQuery = Read>; #[inline] fn render<'w>( - _view: Entity, - item: Entity, - (meshes, mesh_query): SystemParamItem<'w, '_, Self::Param>, + _item: &P, + _view: (), + mesh_handle: ROQueryItem<'_, Self::ItemWorldQuery>, + meshes: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let mesh_handle = mesh_query.get(item).unwrap(); if let Some(gpu_mesh) = meshes.into_inner().get(mesh_handle) { pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); match &gpu_mesh.buffer_info { diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 4f4c5495dbaa37..f851862f250c64 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -196,38 +196,35 @@ fn pbr( // point lights for (var i: u32 = offset_and_counts[0]; i < offset_and_counts[0] + offset_and_counts[1]; i = i + 1u) { let light_id = get_light_id(i); - let light = point_lights.data[light_id]; var shadow: f32 = 1.0; if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u - && (light.flags & POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { + && (point_lights.data[light_id].flags & POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { shadow = fetch_point_shadow(light_id, in.world_position, in.world_normal); } - let light_contrib = point_light(in.world_position.xyz, light, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); + let light_contrib = point_light(in.world_position.xyz, light_id, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); light_accum = light_accum + light_contrib * shadow; } // spot lights for (var i: u32 = offset_and_counts[0] + offset_and_counts[1]; i < offset_and_counts[0] + offset_and_counts[1] + offset_and_counts[2]; i = i + 1u) { let light_id = get_light_id(i); - let light = point_lights.data[light_id]; var shadow: f32 = 1.0; if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u - && (light.flags & POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { + && (point_lights.data[light_id].flags & POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { shadow = fetch_spot_shadow(light_id, in.world_position, in.world_normal); } - let light_contrib = spot_light(in.world_position.xyz, light, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); + let light_contrib = spot_light(in.world_position.xyz, light_id, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); light_accum = light_accum + light_contrib * shadow; } let n_directional_lights = lights.n_directional_lights; for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) { - let light = lights.directional_lights[i]; var shadow: f32 = 1.0; if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u - && (light.flags & DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { + && (lights.directional_lights[i].flags & DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { shadow = fetch_directional_shadow(i, in.world_position, in.world_normal); } - let light_contrib = directional_light(light, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); + let light_contrib = directional_light(i, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); light_accum = light_accum + light_contrib * shadow; } diff --git a/crates/bevy_pbr/src/render/pbr_lighting.wgsl b/crates/bevy_pbr/src/render/pbr_lighting.wgsl index 97ad2dd687b1cc..88a5ea385f57d2 100644 --- a/crates/bevy_pbr/src/render/pbr_lighting.wgsl +++ b/crates/bevy_pbr/src/render/pbr_lighting.wgsl @@ -150,22 +150,23 @@ fn perceptualRoughnessToRoughness(perceptualRoughness: f32) -> f32 { } fn point_light( - world_position: vec3, light: PointLight, roughness: f32, NdotV: f32, N: vec3, V: vec3, + world_position: vec3, light_id: u32, roughness: f32, NdotV: f32, N: vec3, V: vec3, R: vec3, F0: vec3, diffuseColor: vec3 ) -> vec3 { - let light_to_frag = light.position_radius.xyz - world_position.xyz; + let light = &point_lights.data[light_id]; + let light_to_frag = (*light).position_radius.xyz - world_position.xyz; let distance_square = dot(light_to_frag, light_to_frag); let rangeAttenuation = - getDistanceAttenuation(distance_square, light.color_inverse_square_range.w); + getDistanceAttenuation(distance_square, (*light).color_inverse_square_range.w); // Specular. // Representative Point Area Lights. // see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p14-16 let a = roughness; let centerToRay = dot(light_to_frag, R) * R - light_to_frag; - let closestPoint = light_to_frag + centerToRay * saturate(light.position_radius.w * inverseSqrt(dot(centerToRay, centerToRay))); + let closestPoint = light_to_frag + centerToRay * saturate((*light).position_radius.w * inverseSqrt(dot(centerToRay, centerToRay))); let LspecLengthInverse = inverseSqrt(dot(closestPoint, closestPoint)); - let normalizationFactor = a / saturate(a + (light.position_radius.w * 0.5 * LspecLengthInverse)); + let normalizationFactor = a / saturate(a + ((*light).position_radius.w * 0.5 * LspecLengthInverse)); let specularIntensity = normalizationFactor * normalizationFactor; var L: vec3 = closestPoint * LspecLengthInverse; // normalize() equivalent? @@ -197,40 +198,44 @@ fn point_light( // I = Φ / 4 π // The derivation of this can be seen here: https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminousPower - // NOTE: light.color.rgb is premultiplied with light.intensity / 4 π (which would be the luminous intensity) on the CPU + // NOTE: (*light).color.rgb is premultiplied with (*light).intensity / 4 π (which would be the luminous intensity) on the CPU // TODO compensate for energy loss https://google.github.io/filament/Filament.html#materialsystem/improvingthebrdfs/energylossinspecularreflectance - return ((diffuse + specular_light) * light.color_inverse_square_range.rgb) * (rangeAttenuation * NoL); + return ((diffuse + specular_light) * (*light).color_inverse_square_range.rgb) * (rangeAttenuation * NoL); } fn spot_light( - world_position: vec3, light: PointLight, roughness: f32, NdotV: f32, N: vec3, V: vec3, + world_position: vec3, light_id: u32, roughness: f32, NdotV: f32, N: vec3, V: vec3, R: vec3, F0: vec3, diffuseColor: vec3 ) -> vec3 { // reuse the point light calculations - let point_light = point_light(world_position, light, roughness, NdotV, N, V, R, F0, diffuseColor); + let point_light = point_light(world_position, light_id, roughness, NdotV, N, V, R, F0, diffuseColor); + + let light = &point_lights.data[light_id]; // reconstruct spot dir from x/z and y-direction flag - var spot_dir = vec3(light.light_custom_data.x, 0.0, light.light_custom_data.y); + var spot_dir = vec3((*light).light_custom_data.x, 0.0, (*light).light_custom_data.y); spot_dir.y = sqrt(max(0.0, 1.0 - spot_dir.x * spot_dir.x - spot_dir.z * spot_dir.z)); - if ((light.flags & POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE) != 0u) { + if (((*light).flags & POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE) != 0u) { spot_dir.y = -spot_dir.y; } - let light_to_frag = light.position_radius.xyz - world_position.xyz; + let light_to_frag = (*light).position_radius.xyz - world_position.xyz; // calculate attenuation based on filament formula https://google.github.io/filament/Filament.html#listing_glslpunctuallight // spot_scale and spot_offset have been precomputed // note we normalize here to get "l" from the filament listing. spot_dir is already normalized let cd = dot(-spot_dir, normalize(light_to_frag)); - let attenuation = saturate(cd * light.light_custom_data.z + light.light_custom_data.w); + let attenuation = saturate(cd * (*light).light_custom_data.z + (*light).light_custom_data.w); let spot_attenuation = attenuation * attenuation; return point_light * spot_attenuation; } -fn directional_light(light: DirectionalLight, roughness: f32, NdotV: f32, normal: vec3, view: vec3, R: vec3, F0: vec3, diffuseColor: vec3) -> vec3 { - let incident_light = light.direction_to_light.xyz; +fn directional_light(light_id: u32, roughness: f32, NdotV: f32, normal: vec3, view: vec3, R: vec3, F0: vec3, diffuseColor: vec3) -> vec3 { + let light = &lights.directional_lights[light_id]; + + let incident_light = (*light).direction_to_light.xyz; let half_vector = normalize(incident_light + view); let NoL = saturate(dot(normal, incident_light)); @@ -241,5 +246,5 @@ fn directional_light(light: DirectionalLight, roughness: f32, NdotV: f32, normal let specularIntensity = 1.0; let specular_light = specular(F0, roughness, half_vector, NdotV, NoL, NoH, LoH, specularIntensity); - return (specular_light + diffuse) * light.color.rgb * NoL; + return (specular_light + diffuse) * (*light).color.rgb * NoL; } diff --git a/crates/bevy_pbr/src/render/shadows.wgsl b/crates/bevy_pbr/src/render/shadows.wgsl index 3953edc041a10f..70c2ed3383a2e9 100644 --- a/crates/bevy_pbr/src/render/shadows.wgsl +++ b/crates/bevy_pbr/src/render/shadows.wgsl @@ -1,23 +1,23 @@ #define_import_path bevy_pbr::shadows fn fetch_point_shadow(light_id: u32, frag_position: vec4, surface_normal: vec3) -> f32 { - let light = point_lights.data[light_id]; + let light = &point_lights.data[light_id]; // because the shadow maps align with the axes and the frustum planes are at 45 degrees // we can get the worldspace depth by taking the largest absolute axis - let surface_to_light = light.position_radius.xyz - frag_position.xyz; + let surface_to_light = (*light).position_radius.xyz - frag_position.xyz; let surface_to_light_abs = abs(surface_to_light); let distance_to_light = max(surface_to_light_abs.x, max(surface_to_light_abs.y, surface_to_light_abs.z)); // The normal bias here is already scaled by the texel size at 1 world unit from the light. // The texel size increases proportionally with distance from the light so multiplying by // distance to light scales the normal bias to the texel size at the fragment distance. - let normal_offset = light.shadow_normal_bias * distance_to_light * surface_normal.xyz; - let depth_offset = light.shadow_depth_bias * normalize(surface_to_light.xyz); + let normal_offset = (*light).shadow_normal_bias * distance_to_light * surface_normal.xyz; + let depth_offset = (*light).shadow_depth_bias * normalize(surface_to_light.xyz); 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 = (*light).position_radius.xyz - offset_position.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)); @@ -25,7 +25,7 @@ fn fetch_point_shadow(light_id: u32, frag_position: vec4, surface_normal: v // projection * vec4(0, 0, -major_axis_magnitude, 1.0) // and keeping only the terms that have any impact on the depth. // Projection-agnostic approach: - let zw = -major_axis_magnitude * light.light_custom_data.xy + light.light_custom_data.zw; + 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 @@ -42,27 +42,27 @@ fn fetch_point_shadow(light_id: u32, frag_position: vec4, surface_normal: v } fn fetch_spot_shadow(light_id: u32, frag_position: vec4, surface_normal: vec3) -> f32 { - let light = point_lights.data[light_id]; + let light = &point_lights.data[light_id]; - let surface_to_light = light.position_radius.xyz - frag_position.xyz; + let surface_to_light = (*light).position_radius.xyz - frag_position.xyz; // construct the light view matrix - var spot_dir = vec3(light.light_custom_data.x, 0.0, light.light_custom_data.y); + var spot_dir = vec3((*light).light_custom_data.x, 0.0, (*light).light_custom_data.y); // reconstruct spot dir from x/z and y-direction flag spot_dir.y = sqrt(1.0 - spot_dir.x * spot_dir.x - spot_dir.z * spot_dir.z); - if ((light.flags & POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE) != 0u) { + if (((*light).flags & POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE) != 0u) { spot_dir.y = -spot_dir.y; } // view matrix z_axis is the reverse of transform.forward() let fwd = -spot_dir; let distance_to_light = dot(fwd, surface_to_light); - let offset_position = - -surface_to_light - + (light.shadow_depth_bias * normalize(surface_to_light)) - + (surface_normal.xyz * light.shadow_normal_bias) * distance_to_light; + let offset_position = + -surface_to_light + + ((*light).shadow_depth_bias * normalize(surface_to_light)) + + (surface_normal.xyz * (*light).shadow_normal_bias) * distance_to_light; - // the construction of the up and right vectors needs to precisely mirror the code + // the construction of the up and right vectors needs to precisely mirror the code // in render/light.rs:spot_light_view_matrix var sign = -1.0; if (fwd.z >= 0.0) { @@ -74,14 +74,14 @@ fn fetch_spot_shadow(light_id: u32, frag_position: vec4, surface_normal: ve let right_dir = vec3(-b, -sign - fwd.y * fwd.y * a, fwd.y); let light_inv_rot = mat3x3(right_dir, up_dir, fwd); - // because the matrix is a pure rotation matrix, the inverse is just the transpose, and to calculate - // the product of the transpose with a vector we can just post-multiply instead of pre-multplying. + // because the matrix is a pure rotation matrix, the inverse is just the transpose, and to calculate + // the product of the transpose with a vector we can just post-multiply instead of pre-multplying. // this allows us to keep the matrix construction code identical between CPU and GPU. let projected_position = offset_position * light_inv_rot; // divide xy by perspective matrix "f" and by -projected.z (projected.z is -projection matrix's w) // to get ndc coordinates - let f_div_minus_z = 1.0 / (light.spot_light_tan_angle * -projected_position.z); + let f_div_minus_z = 1.0 / ((*light).spot_light_tan_angle * -projected_position.z); let shadow_xy_ndc = projected_position.xy * f_div_minus_z; // convert to uv coordinates let shadow_uv = shadow_xy_ndc * vec2(0.5, -0.5) + vec2(0.5, 0.5); @@ -90,23 +90,23 @@ fn fetch_spot_shadow(light_id: u32, frag_position: vec4, surface_normal: ve let depth = 0.1 / -projected_position.z; #ifdef NO_ARRAY_TEXTURES_SUPPORT - return textureSampleCompare(directional_shadow_textures, directional_shadow_textures_sampler, + return textureSampleCompare(directional_shadow_textures, directional_shadow_textures_sampler, shadow_uv, depth); #else - return textureSampleCompareLevel(directional_shadow_textures, directional_shadow_textures_sampler, + return textureSampleCompareLevel(directional_shadow_textures, directional_shadow_textures_sampler, shadow_uv, i32(light_id) + lights.spot_light_shadowmap_offset, depth); #endif } fn fetch_directional_shadow(light_id: u32, frag_position: vec4, surface_normal: vec3) -> f32 { - let light = lights.directional_lights[light_id]; + let light = &lights.directional_lights[light_id]; // The normal bias is scaled to the texel size. - let normal_offset = light.shadow_normal_bias * surface_normal.xyz; - let depth_offset = light.shadow_depth_bias * light.direction_to_light.xyz; + let normal_offset = (*light).shadow_normal_bias * surface_normal.xyz; + let depth_offset = (*light).shadow_depth_bias * (*light).direction_to_light.xyz; let offset_position = vec4(frag_position.xyz + normal_offset + depth_offset, frag_position.w); - let offset_position_clip = light.view_projection * offset_position; + let offset_position_clip = (*light).view_projection * offset_position; if (offset_position_clip.w <= 0.0) { return 1.0; } diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 4aec221350cb67..bf38fd5ff1d9ff 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -2,6 +2,7 @@ #![no_std] #![warn(missing_docs)] +use core::fmt::{self, Formatter, Pointer}; use core::{ cell::UnsafeCell, marker::PhantomData, mem::ManuallyDrop, num::NonZeroUsize, ptr::NonNull, }; @@ -94,6 +95,13 @@ macro_rules! impl_ptr { Self(inner, PhantomData) } } + + impl Pointer for $ptr<'_> { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Pointer::fmt(&self.0, f) + } + } }; } @@ -168,6 +176,20 @@ impl<'a> PtrMut<'a> { pub fn as_ptr(&self) -> *mut u8 { self.0.as_ptr() } + + /// Gets a `PtrMut` from this with a smaller lifetime. + #[inline] + pub fn reborrow(&mut self) -> PtrMut<'_> { + // SAFE: the ptrmut we're borrowing from is assumed to be valid + unsafe { PtrMut::new(self.0) } + } + + /// Gets an immutable reference from this mutable reference + #[inline] + pub fn as_ref(&self) -> Ptr<'_> { + // SAFE: The `PtrMut` type's guarantees about the validity of this pointer are a superset of `Ptr` s guarantees + unsafe { Ptr::new(self.0) } + } } impl<'a, T> From<&'a mut T> for PtrMut<'a> { @@ -216,6 +238,20 @@ impl<'a> OwningPtr<'a> { pub fn as_ptr(&self) -> *mut u8 { self.0.as_ptr() } + + /// Gets an immutable pointer from this owned pointer. + #[inline] + pub fn as_ref(&self) -> Ptr<'_> { + // SAFE: The `Owning` type's guarantees about the validity of this pointer are a superset of `Ptr` s guarantees + unsafe { Ptr::new(self.0) } + } + + /// Gets a mutable pointer from this owned pointer. + #[inline] + pub fn as_mut(&mut self) -> PtrMut<'_> { + // SAFE: The `Owning` type's guarantees about the validity of this pointer are a superset of `Ptr` s guarantees + unsafe { PtrMut::new(self.0) } + } } /// Conceptually equivalent to `&'a [T]` but with length information cut out for performance reasons diff --git a/crates/bevy_reflect_compile_fail_tests/Cargo.toml b/crates/bevy_reflect_compile_fail_tests/Cargo.toml new file mode 100644 index 00000000000000..5af57acd621f85 --- /dev/null +++ b/crates/bevy_reflect_compile_fail_tests/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "bevy_reflect_compile_fail_tests" +version = "0.1.0" +edition = "2021" +description = "Compile fail tests for Bevy Engine's reflection system" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +publish = false + +[dev-dependencies] +bevy_reflect = { path = "../bevy_reflect" } +trybuild = "1.0.71" diff --git a/crates/bevy_reflect_compile_fail_tests/README.md b/crates/bevy_reflect_compile_fail_tests/README.md new file mode 100644 index 00000000000000..52faa4f1d60348 --- /dev/null +++ b/crates/bevy_reflect_compile_fail_tests/README.md @@ -0,0 +1,7 @@ +# Compile fail tests for bevy_reflect + +This crate is separate from `bevy_reflect` and not part of the Bevy workspace in order to not fail `crater` tests for +Bevy. +The tests assert on the exact compiler errors and can easily fail for new Rust versions due to updated compiler errors (e.g. changes in spans). + +The `CI` workflow executes these tests on the stable rust toolchain (see [tools/ci](../../tools/ci/src/main.rs)). diff --git a/crates/bevy_reflect_compile_fail_tests/src/lib.rs b/crates/bevy_reflect_compile_fail_tests/src/lib.rs new file mode 100644 index 00000000000000..d0d1683dd6b97c --- /dev/null +++ b/crates/bevy_reflect_compile_fail_tests/src/lib.rs @@ -0,0 +1 @@ +// Nothing here, check out the integration tests diff --git a/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive.rs b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive.rs new file mode 100644 index 00000000000000..dbab6a4ef08f68 --- /dev/null +++ b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive.rs @@ -0,0 +1,6 @@ +#[test] +fn test() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/reflect_derive/*.fail.rs"); + t.pass("tests/reflect_derive/*.pass.rs"); +} diff --git a/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/lifetimes.fail.rs b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/lifetimes.fail.rs new file mode 100644 index 00000000000000..4a97e5d8278a2f --- /dev/null +++ b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/lifetimes.fail.rs @@ -0,0 +1,9 @@ +use bevy_reflect::Reflect; + +#[derive(Reflect)] +struct Foo<'a> { + #[reflect(ignore)] + value: &'a str, +} + +fn main() {} diff --git a/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/lifetimes.fail.stderr b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/lifetimes.fail.stderr new file mode 100644 index 00000000000000..156fb6cda17d7c --- /dev/null +++ b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/lifetimes.fail.stderr @@ -0,0 +1,13 @@ +error[E0478]: lifetime bound not satisfied + --> tests/reflect_derive/lifetimes.fail.rs:3:10 + | +3 | #[derive(Reflect)] + | ^^^^^^^ + | +note: lifetime parameter instantiated with the lifetime `'a` as defined here + --> tests/reflect_derive/lifetimes.fail.rs:4:12 + | +4 | struct Foo<'a> { + | ^^ + = note: but lifetime parameter must outlive the static lifetime + = note: this error originates in the derive macro `Reflect` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/lifetimes.pass.rs b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/lifetimes.pass.rs new file mode 100644 index 00000000000000..60d32b81f38ec5 --- /dev/null +++ b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/lifetimes.pass.rs @@ -0,0 +1,10 @@ +use bevy_reflect::Reflect; + +// Reason: Reflection relies on `Any` which requires `'static` +#[derive(Reflect)] +struct Foo<'a: 'static> { + #[reflect(ignore)] + value: &'a str, +} + +fn main() {} diff --git a/crates/bevy_render/macros/src/as_bind_group.rs b/crates/bevy_render/macros/src/as_bind_group.rs index 4c8a2b14b2ac9b..bbc3c3850cf0b6 100644 --- a/crates/bevy_render/macros/src/as_bind_group.rs +++ b/crates/bevy_render/macros/src/as_bind_group.rs @@ -371,7 +371,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { render_device: &#render_path::renderer::RenderDevice, images: &#render_path::render_asset::RenderAssets<#render_path::texture::Image>, fallback_image: &#render_path::texture::FallbackImage, - ) -> Result<#render_path::render_resource::PreparedBindGroup, #render_path::render_resource::AsBindGroupError> { + ) -> Result<#render_path::render_resource::PreparedBindGroup, #render_path::render_resource::AsBindGroupError> { let bindings = vec![#(#binding_impls,)*]; let bind_group = { diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 7d986c9a3125a1..72bcfa3556df07 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -86,8 +86,8 @@ pub struct ComputedCameraValues { pub struct Camera { /// If set, this camera will render to the given [`Viewport`] rectangle within the configured [`RenderTarget`]. pub viewport: Option, - /// Cameras with a lower priority will be rendered before cameras with a higher priority. - pub priority: isize, + /// Cameras with a higher order are rendered later, and thus on top of lower order cameras. + pub order: isize, /// If this is set to `true`, this camera will be rendered to its specified [`RenderTarget`]. If `false`, this /// camera will not be rendered. pub is_active: bool, @@ -109,7 +109,7 @@ impl Default for Camera { fn default() -> Self { Self { is_active: true, - priority: 0, + order: 0, viewport: None, computed: Default::default(), target: Default::default(), @@ -477,7 +477,7 @@ pub struct ExtractedCamera { pub physical_target_size: Option, pub viewport: Option, pub render_graph: Cow<'static, str>, - pub priority: isize, + pub order: isize, } pub fn extract_cameras( @@ -511,7 +511,7 @@ pub fn extract_cameras( physical_viewport_size: Some(viewport_size), physical_target_size: Some(target_size), render_graph: camera_render_graph.0.clone(), - priority: camera.priority, + order: camera.order, }, ExtractedView { projection: camera.projection_matrix(), diff --git a/crates/bevy_render/src/camera/camera_driver_node.rs b/crates/bevy_render/src/camera/camera_driver_node.rs index 224e61e787a92f..f57929f30caeba 100644 --- a/crates/bevy_render/src/camera/camera_driver_node.rs +++ b/crates/bevy_render/src/camera/camera_driver_node.rs @@ -33,24 +33,24 @@ impl Node for CameraDriverNode { let mut sorted_cameras = self .cameras .iter_manual(world) - .map(|(e, c)| (e, c.priority, c.target.clone())) + .map(|(e, c)| (e, c.order, c.target.clone())) .collect::>(); - // sort by priority and ensure within a priority, RenderTargets of the same type are packed together + // sort by order and ensure within an order, RenderTargets of the same type are packed together sorted_cameras.sort_by(|(_, p1, t1), (_, p2, t2)| match p1.cmp(p2) { std::cmp::Ordering::Equal => t1.cmp(t2), ord => ord, }); let mut camera_windows = HashSet::new(); - let mut previous_priority_target = None; + let mut previous_order_target = None; let mut ambiguities = HashSet::new(); - for (entity, priority, target) in sorted_cameras { - let new_priority_target = (priority, target); - if let Some(previous_priority_target) = previous_priority_target { - if previous_priority_target == new_priority_target { - ambiguities.insert(new_priority_target.clone()); + for (entity, order, target) in sorted_cameras { + let new_order_target = (order, target); + if let Some(previous_order_target) = previous_order_target { + if previous_order_target == new_order_target { + ambiguities.insert(new_order_target.clone()); } } - previous_priority_target = Some(new_priority_target); + previous_order_target = Some(new_order_target); if let Ok((_, camera)) = self.cameras.get_manual(world, entity) { if let RenderTarget::Window(id) = camera.target { camera_windows.insert(id); @@ -62,8 +62,8 @@ impl Node for CameraDriverNode { if !ambiguities.is_empty() { warn!( - "Camera priority ambiguities detected for active cameras with the following priorities: {:?}. \ - To fix this, ensure there is exactly one Camera entity spawned with a given priority for a given RenderTarget. \ + "Camera order ambiguities detected for active cameras with the following priorities: {:?}. \ + To fix this, ensure there is exactly one Camera entity spawned with a given order for a given RenderTarget. \ Ambiguities should be resolved because either (1) multiple active cameras were spawned accidentally, which will \ result in rendering multiple instances of the scene or (2) for cases where multiple active cameras is intentional, \ ambiguities could result in unpredictable render results.", diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index f595023588e768..05f3a7a13ca5f2 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -1,3 +1,6 @@ +#[cfg(target_pointer_width = "16")] +compile_error!("bevy_render cannot compile for a 16-bit platform."); + extern crate core; pub mod camera; @@ -8,7 +11,6 @@ pub mod extract_resource; pub mod globals; pub mod mesh; pub mod primitives; -pub mod rangefinder; pub mod render_asset; pub mod render_graph; pub mod render_phase; @@ -43,6 +45,7 @@ use crate::{ mesh::MeshPlugin, render_resource::{PipelineCache, Shader, ShaderLoader}, renderer::{render_system, RenderInstance}, + settings::WgpuSettings, view::{ViewPlugin, WindowRenderPlugin}, }; use bevy_app::{App, AppLabel, Plugin}; @@ -56,7 +59,9 @@ use std::{ /// Contains the default Bevy rendering backend based on wgpu. #[derive(Default)] -pub struct RenderPlugin; +pub struct RenderPlugin { + pub wgpu_settings: WgpuSettings, +} /// The labels of the default App rendering stages. #[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] @@ -124,18 +129,12 @@ pub struct RenderApp; impl Plugin for RenderPlugin { /// Initializes the renderer, sets up the [`RenderStage`](RenderStage) and creates the rendering sub-app. fn build(&self, app: &mut App) { - let options = app - .world - .get_resource::() - .cloned() - .unwrap_or_default(); - app.add_asset::() .add_debug_asset::() .init_asset_loader::() .init_debug_asset_loader::(); - if let Some(backends) = options.backends { + if let Some(backends) = self.wgpu_settings.backends { let windows = app.world.resource_mut::(); let instance = wgpu::Instance::new(backends); @@ -148,13 +147,16 @@ impl Plugin for RenderPlugin { }); let request_adapter_options = wgpu::RequestAdapterOptions { - power_preference: options.power_preference, + power_preference: self.wgpu_settings.power_preference, compatible_surface: surface.as_ref(), ..Default::default() }; - let (device, queue, adapter_info, render_adapter) = futures_lite::future::block_on( - renderer::initialize_renderer(&instance, &options, &request_adapter_options), - ); + let (device, queue, adapter_info, render_adapter) = + futures_lite::future::block_on(renderer::initialize_renderer( + &instance, + &self.wgpu_settings, + &request_adapter_options, + )); debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); debug!("Configured wgpu adapter Features: {:#?}", device.features()); app.insert_resource(device.clone()) diff --git a/crates/bevy_render/src/mesh/shape/cylinder.rs b/crates/bevy_render/src/mesh/shape/cylinder.rs index 844be9ba6430ea..f20981d23584f3 100644 --- a/crates/bevy_render/src/mesh/shape/cylinder.rs +++ b/crates/bevy_render/src/mesh/shape/cylinder.rs @@ -103,7 +103,7 @@ impl From for Mesh { uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]); } - for i in 1..(c.resolution as u32 - 1) { + for i in 1..(c.resolution - 1) { indices.extend_from_slice(&[ offset, offset + i + winding.0, diff --git a/crates/bevy_render/src/mesh/shape/mod.rs b/crates/bevy_render/src/mesh/shape/mod.rs index a2a76ccd12fbc0..fd8f1873edae12 100644 --- a/crates/bevy_render/src/mesh/shape/mod.rs +++ b/crates/bevy_render/src/mesh/shape/mod.rs @@ -73,13 +73,14 @@ impl Default for Box { impl From for Mesh { fn from(sp: Box) -> Self { + // suppose Y-up right hand, and camera look from +z to -z let vertices = &[ - // Top + // Front ([sp.min_x, sp.min_y, sp.max_z], [0., 0., 1.0], [0., 0.]), ([sp.max_x, sp.min_y, sp.max_z], [0., 0., 1.0], [1.0, 0.]), ([sp.max_x, sp.max_y, sp.max_z], [0., 0., 1.0], [1.0, 1.0]), ([sp.min_x, sp.max_y, sp.max_z], [0., 0., 1.0], [0., 1.0]), - // Bottom + // Back ([sp.min_x, sp.max_y, sp.min_z], [0., 0., -1.0], [1.0, 0.]), ([sp.max_x, sp.max_y, sp.min_z], [0., 0., -1.0], [0., 0.]), ([sp.max_x, sp.min_y, sp.min_z], [0., 0., -1.0], [0., 1.0]), @@ -94,12 +95,12 @@ impl From for Mesh { ([sp.min_x, sp.max_y, sp.max_z], [-1.0, 0., 0.], [0., 0.]), ([sp.min_x, sp.max_y, sp.min_z], [-1.0, 0., 0.], [0., 1.0]), ([sp.min_x, sp.min_y, sp.min_z], [-1.0, 0., 0.], [1.0, 1.0]), - // Front + // Top ([sp.max_x, sp.max_y, sp.min_z], [0., 1.0, 0.], [1.0, 0.]), ([sp.min_x, sp.max_y, sp.min_z], [0., 1.0, 0.], [0., 0.]), ([sp.min_x, sp.max_y, sp.max_z], [0., 1.0, 0.], [0., 1.0]), ([sp.max_x, sp.max_y, sp.max_z], [0., 1.0, 0.], [1.0, 1.0]), - // Back + // Bottom ([sp.max_x, sp.min_y, sp.max_z], [0., -1.0, 0.], [0., 0.]), ([sp.min_x, sp.min_y, sp.max_z], [0., -1.0, 0.], [1.0, 0.]), ([sp.min_x, sp.min_y, sp.min_z], [0., -1.0, 0.], [1.0, 1.0]), @@ -111,12 +112,12 @@ impl From for Mesh { let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect(); let indices = Indices::U32(vec![ - 0, 1, 2, 2, 3, 0, // top - 4, 5, 6, 6, 7, 4, // bottom + 0, 1, 2, 2, 3, 0, // front + 4, 5, 6, 6, 7, 4, // back 8, 9, 10, 10, 11, 8, // right 12, 13, 14, 14, 15, 12, // left - 16, 17, 18, 18, 19, 16, // front - 20, 21, 22, 22, 23, 20, // back + 16, 17, 18, 18, 19, 16, // top + 20, 21, 22, 22, 23, 20, // bottom ]); let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); diff --git a/crates/bevy_render/src/render_graph/node.rs b/crates/bevy_render/src/render_graph/node.rs index ab60925e6fbe14..9e3e884ba8b082 100644 --- a/crates/bevy_render/src/render_graph/node.rs +++ b/crates/bevy_render/src/render_graph/node.rs @@ -1,4 +1,5 @@ use crate::{ + define_atomic_id, render_graph::{ Edge, InputSlotError, OutputSlotError, RenderGraphContext, RenderGraphError, RunSubGraphError, SlotInfo, SlotInfos, SlotType, SlotValue, @@ -6,28 +7,11 @@ use crate::{ renderer::RenderContext, }; use bevy_ecs::world::World; -use bevy_utils::Uuid; use downcast_rs::{impl_downcast, Downcast}; use std::{borrow::Cow, fmt::Debug}; use thiserror::Error; -/// A [`Node`] identifier. -/// It automatically generates its own random uuid. -/// -/// This id is used to reference the node internally (edges, etc). -#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct NodeId(Uuid); - -impl NodeId { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - NodeId(Uuid::new_v4()) - } - - pub fn uuid(&self) -> &Uuid { - &self.0 - } -} +define_atomic_id!(NodeId); /// A render node that can be added to a [`RenderGraph`](super::RenderGraph). /// diff --git a/crates/bevy_render/src/render_phase/draw.rs b/crates/bevy_render/src/render_phase/draw.rs index 67ff987b7972d4..74bcb473561b1f 100644 --- a/crates/bevy_render/src/render_phase/draw.rs +++ b/crates/bevy_render/src/render_phase/draw.rs @@ -1,11 +1,12 @@ use crate::{ - render_phase::TrackedRenderPass, + render_phase::{PhaseItem, TrackedRenderPass}, render_resource::{CachedRenderPipelineId, PipelineCache}, }; use bevy_app::App; use bevy_ecs::{ all_tuples, entity::Entity, + query::{QueryState, ROQueryItem, ReadOnlyWorldQuery}, system::{ lifetimeless::SRes, ReadOnlySystemParam, Resource, SystemParam, SystemParamItem, SystemState, @@ -14,14 +15,23 @@ use bevy_ecs::{ }; use bevy_utils::HashMap; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; -use std::{any::TypeId, fmt::Debug, hash::Hash, ops::Range}; +use std::{any::TypeId, fmt::Debug, hash::Hash}; -/// A draw function which is used to draw a specific [`PhaseItem`]. +// Todo: consider renaming this to `DrawFunction` +/// A draw function used to draw [`PhaseItem`]s. /// -/// They are the general form of drawing items, whereas [`RenderCommands`](RenderCommand) -/// are more modular. +/// Therefore the draw function can retrieve and query the required ECS data from the render world. +/// +/// This trait can either be implemented directly or implicitly composed out of multiple modular +/// [`RenderCommand`]s. pub trait Draw: Send + Sync + 'static { - /// Draws the [`PhaseItem`] by issuing draw calls via the [`TrackedRenderPass`]. + /// Prepares the draw function to be used. This is called once and only once before the phase + /// begins. There may be zero or more `draw` calls following a call to this function. + /// Implementing this is optional. + #[allow(unused_variables)] + fn prepare(&mut self, world: &'_ World) {} + + /// Draws a [`PhaseItem`] by issuing one or more draw calls via the [`TrackedRenderPass`]. fn draw<'w>( &mut self, world: &'w World, @@ -31,66 +41,43 @@ pub trait Draw: Send + Sync + 'static { ); } -/// An item which will be drawn to the screen. A phase item should be queued up for rendering -/// during the [`RenderStage::Queue`](crate::RenderStage::Queue) stage. -/// Afterwards it will be sorted and rendered automatically in the -/// [`RenderStage::PhaseSort`](crate::RenderStage::PhaseSort) stage and -/// [`RenderStage::Render`](crate::RenderStage::Render) stage, respectively. -pub trait PhaseItem: Sized + Send + Sync + 'static { - /// The type used for ordering the items. The smallest values are drawn first. - type SortKey: Ord; - /// Determines the order in which the items are drawn during the corresponding [`RenderPhase`](super::RenderPhase). - fn sort_key(&self) -> Self::SortKey; - /// Specifies the [`Draw`] function used to render the item. - fn draw_function(&self) -> DrawFunctionId; - - /// Sorts a slice of phase items into render order. Generally if the same type - /// implements [`BatchedPhaseItem`], this should use a stable sort like [`slice::sort_by_key`]. - /// In almost all other cases, this should not be altered from the default, - /// which uses a unstable sort, as this provides the best balance of CPU and GPU - /// performance. - /// - /// Implementers can optionally not sort the list at all. This is generally advisable if and - /// only if the renderer supports a depth prepass, which is by default not supported by - /// the rest of Bevy's first party rendering crates. Even then, this may have a negative - /// impact on GPU-side performance due to overdraw. - /// - /// It's advised to always profile for performance changes when changing this implementation. - #[inline] - fn sort(items: &mut [Self]) { - items.sort_unstable_by_key(|item| item.sort_key()); - } -} - // TODO: make this generic? /// An identifier for a [`Draw`] function stored in [`DrawFunctions`]. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct DrawFunctionId(usize); +pub struct DrawFunctionId(u32); -/// Stores all draw functions for the [`PhaseItem`] type. -/// For retrieval they are associated with their [`TypeId`]. +/// Stores all [`Draw`] functions for the [`PhaseItem`] type. +/// +/// For retrieval, the [`Draw`] functions are mapped to their respective [`TypeId`]s. pub struct DrawFunctionsInternal { pub draw_functions: Vec>>, pub indices: HashMap, } impl DrawFunctionsInternal

{ - /// Adds the [`Draw`] function and associates it to its own type. + /// Prepares all draw function. This is called once and only once before the phase begins. + pub fn prepare(&mut self, world: &World) { + for function in &mut self.draw_functions { + function.prepare(world); + } + } + + /// Adds the [`Draw`] function and maps it to its own type. pub fn add>(&mut self, draw_function: T) -> DrawFunctionId { self.add_with::(draw_function) } - /// Adds the [`Draw`] function and associates it to the type `T` + /// Adds the [`Draw`] function and maps it to the type `T` pub fn add_with>(&mut self, draw_function: D) -> DrawFunctionId { + let id = DrawFunctionId(self.draw_functions.len().try_into().unwrap()); self.draw_functions.push(Box::new(draw_function)); - let id = DrawFunctionId(self.draw_functions.len() - 1); self.indices.insert(TypeId::of::(), id); id } /// Retrieves the [`Draw`] function corresponding to the `id` mutably. pub fn get_mut(&mut self, id: DrawFunctionId) -> Option<&mut dyn Draw

> { - self.draw_functions.get_mut(id.0).map(|f| &mut **f) + self.draw_functions.get_mut(id.0 as usize).map(|f| &mut **f) } /// Retrieves the id of the [`Draw`] function corresponding to their associated type `T`. @@ -103,7 +90,7 @@ impl DrawFunctionsInternal

{ /// Fallible wrapper for [`Self::get_id()`] /// /// ## Panics - /// If the id doesn't exist it will panic + /// If the id doesn't exist, this function will panic. pub fn id(&self) -> DrawFunctionId { self.get_id::().unwrap_or_else(|| { panic!( @@ -116,6 +103,7 @@ impl DrawFunctionsInternal

{ } /// Stores all draw functions for the [`PhaseItem`] type hidden behind a reader-writer lock. +/// /// To access them the [`DrawFunctions::read`] and [`DrawFunctions::write`] methods are used. #[derive(Resource)] pub struct DrawFunctions { @@ -145,15 +133,26 @@ impl DrawFunctions

{ } } -/// [`RenderCommand`] is a trait that runs an ECS query and produces one or more -/// [`TrackedRenderPass`] calls. Types implementing this trait can be composed (as tuples). +/// [`RenderCommand`]s are modular standardized pieces of render logic that can be composed into +/// [`Draw`] functions. /// -/// They can be registered as a [`Draw`] function via the +/// To turn a stateless render command into a usable draw function it has to be wrapped by a +/// [`RenderCommandState`]. +/// This is done automatically when registering a render command as a [`Draw`] function via the /// [`AddRenderCommand::add_render_command`] method. /// +/// Compared to the draw function the required ECS data is fetched automatically +/// (by the [`RenderCommandState`]) from the render world. +/// Therefore the three types [`Param`](RenderCommand::Param), +/// [`ViewWorldQuery`](RenderCommand::ViewWorldQuery) and +/// [`ItemWorldQuery`](RenderCommand::ItemWorldQuery) are used. +/// They specify which information is required to execute the render command. +/// +/// Multiple render commands can be combined together by wrapping them in a tuple. +/// /// # Example /// The `DrawPbr` draw function is created from the following render command -/// tuple. Const generics are used to set specific bind group locations: +/// tuple. Const generics are used to set specific bind group locations: /// /// ```ignore /// pub type DrawPbr = ( @@ -165,140 +164,49 @@ impl DrawFunctions

{ /// ); /// ``` pub trait RenderCommand { - /// Specifies all ECS data required by [`RenderCommand::render`]. + /// Specifies the general ECS data (e.g. resources) required by [`RenderCommand::render`]. /// All parameters have to be read only. type Param: SystemParam + 'static; - - /// Renders the [`PhaseItem`] by issuing draw calls via the [`TrackedRenderPass`]. + /// Specifies the ECS data of the view required by [`RenderCommand::render`]. + /// All components have to be accessed read only. + type ViewWorldQuery: ReadOnlyWorldQuery; + /// Specifies the ECS data of the item required by [`RenderCommand::render`]. + /// All components have to be accessed read only. + type ItemWorldQuery: ReadOnlyWorldQuery; + + /// Renders a [`PhaseItem`] by recording commands (e.g. setting pipelines, binding bind groups, + /// issuing draw calls, etc.) via the [`TrackedRenderPass`]. fn render<'w>( - view: Entity, item: &P, + view: ROQueryItem<'w, Self::ViewWorldQuery>, + entity: ROQueryItem<'w, Self::ItemWorldQuery>, param: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult; } +/// The result of a [`RenderCommand`]. pub enum RenderCommandResult { Success, Failure, } -pub trait EntityRenderCommand { - type Param: SystemParam + 'static; - fn render<'w>( - view: Entity, - item: Entity, - param: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult; -} - -pub trait EntityPhaseItem: PhaseItem { - fn entity(&self) -> Entity; -} - -pub trait CachedRenderPipelinePhaseItem: PhaseItem { - fn cached_pipeline(&self) -> CachedRenderPipelineId; -} - -/// A [`PhaseItem`] that can be batched dynamically. -/// -/// Batching is an optimization that regroups multiple items in the same vertex buffer -/// to render them in a single draw call. -/// -/// If this is implemented on a type, the implementation of [`PhaseItem::sort`] should -/// be changed to implement a stable sort, or incorrect/suboptimal batching may result. -pub trait BatchedPhaseItem: EntityPhaseItem { - /// Range in the vertex buffer of this item - fn batch_range(&self) -> &Option>; - - /// Range in the vertex buffer of this item - fn batch_range_mut(&mut self) -> &mut Option>; - - /// Batches another item within this item if they are compatible. - /// Items can be batched together if they have the same entity, and consecutive ranges. - /// If batching is successful, the `other` item should be discarded from the render pass. - #[inline] - fn add_to_batch(&mut self, other: &Self) -> BatchResult { - let self_entity = self.entity(); - if let (Some(self_batch_range), Some(other_batch_range)) = ( - self.batch_range_mut().as_mut(), - other.batch_range().as_ref(), - ) { - // If the items are compatible, join their range into `self` - if self_entity == other.entity() { - if self_batch_range.end == other_batch_range.start { - self_batch_range.end = other_batch_range.end; - return BatchResult::Success; - } else if self_batch_range.start == other_batch_range.end { - self_batch_range.start = other_batch_range.start; - return BatchResult::Success; - } - } - } - BatchResult::IncompatibleItems - } -} - -pub enum BatchResult { - /// The `other` item was batched into `self` - Success, - /// `self` and `other` cannot be batched together - IncompatibleItems, -} - -impl RenderCommand

for E -where - E::Param: 'static, -{ - type Param = E::Param; - - #[inline] - fn render<'w>( - view: Entity, - item: &P, - param: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - ::render(view, item.entity(), param, pass) - } -} - -pub struct SetItemPipeline; -impl RenderCommand

for SetItemPipeline { - type Param = SRes; - #[inline] - fn render<'w>( - _view: Entity, - item: &P, - pipeline_cache: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - if let Some(pipeline) = pipeline_cache - .into_inner() - .get_render_pipeline(item.cached_pipeline()) - { - pass.set_render_pipeline(pipeline); - RenderCommandResult::Success - } else { - RenderCommandResult::Failure - } - } -} - macro_rules! render_command_tuple_impl { - ($($name: ident),*) => { + ($(($name: ident, $view: ident, $entity: ident)),*) => { impl),*> RenderCommand

for ($($name,)*) { type Param = ($($name::Param,)*); + type ViewWorldQuery = ($($name::ViewWorldQuery,)*); + type ItemWorldQuery = ($($name::ItemWorldQuery,)*); #[allow(non_snake_case)] fn render<'w>( - _view: Entity, _item: &P, + ($($view,)*): ROQueryItem<'w, Self::ViewWorldQuery>, + ($($entity,)*): ROQueryItem<'w, Self::ItemWorldQuery>, ($($name,)*): SystemParamItem<'w, '_, Self::Param>, _pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult{ - $(if let RenderCommandResult::Failure = $name::render(_view, _item, $name, _pass) { + ) -> RenderCommandResult { + $(if let RenderCommandResult::Failure = $name::render(_item, $view, $entity, $name, _pass) { return RenderCommandResult::Failure; })* RenderCommandResult::Success @@ -307,18 +215,24 @@ macro_rules! render_command_tuple_impl { }; } -all_tuples!(render_command_tuple_impl, 0, 15, C); +all_tuples!(render_command_tuple_impl, 0, 15, C, V, E); /// Wraps a [`RenderCommand`] into a state so that it can be used as a [`Draw`] function. -/// Therefore the [`RenderCommand::Param`] is queried from the ECS and passed to the command. +/// Therefore the [`RenderCommand::Param`], [`RenderCommand::ViewWorldQuery`] and +/// [`RenderCommand::ItemWorldQuery`] are queried from the ECS and passed to the command. pub struct RenderCommandState> { state: SystemState, + view: QueryState, + entity: QueryState, } impl> RenderCommandState { + /// Creates a new [`RenderCommandState`] for the [`RenderCommand`]. pub fn new(world: &mut World) -> Self { Self { state: SystemState::new(world), + view: world.query(), + entity: world.query(), } } } @@ -327,7 +241,14 @@ impl + Send + Sync + 'static> Draw

for Rend where C::Param: ReadOnlySystemParam, { - /// Prepares the ECS parameters for the wrapped [`RenderCommand`] and then renders it. + /// Prepares the render command to be used. This is called once and only once before the phase + /// begins. There may be zero or more `draw` calls following a call to this function. + fn prepare(&mut self, world: &'_ World) { + self.view.update_archetypes(world); + self.entity.update_archetypes(world); + } + + /// Fetches the ECS parameters for the wrapped [`RenderCommand`] and then renders it. fn draw<'w>( &mut self, world: &'w World, @@ -336,7 +257,10 @@ where item: &P, ) { let param = self.state.get(world); - C::render(view, item, param, pass); + let view = self.view.get_manual(world, view).unwrap(); + let entity = self.entity.get_manual(world, item.entity()).unwrap(); + // Todo: handle/log `RenderCommand` failure + C::render(item, view, entity, param, pass); } } @@ -373,3 +297,35 @@ impl AddRenderCommand for App { self } } + +// Todo: If this is always needed, combine this with PhaseItem? +pub trait CachedRenderPipelinePhaseItem: PhaseItem { + fn cached_pipeline(&self) -> CachedRenderPipelineId; +} + +/// A [`RenderCommand`] that sets the pipeline for the `[PhaseItem]`. +pub struct SetItemPipeline; + +impl RenderCommand

for SetItemPipeline { + type Param = SRes; + type ViewWorldQuery = (); + type ItemWorldQuery = (); + #[inline] + fn render<'w>( + item: &P, + _view: (), + _entity: (), + pipeline_cache: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + if let Some(pipeline) = pipeline_cache + .into_inner() + .get_render_pipeline(item.cached_pipeline()) + { + pass.set_render_pipeline(pipeline); + RenderCommandResult::Success + } else { + RenderCommandResult::Failure + } + } +} diff --git a/crates/bevy_render/src/render_phase/draw_state.rs b/crates/bevy_render/src/render_phase/draw_state.rs index 907712e6788a6f..1c586fbc6031d2 100644 --- a/crates/bevy_render/src/render_phase/draw_state.rs +++ b/crates/bevy_render/src/render_phase/draw_state.rs @@ -10,7 +10,9 @@ use bevy_utils::tracing::trace; use std::ops::Range; use wgpu::{IndexFormat, RenderPass}; -/// Tracks the current [`TrackedRenderPass`] state to ensure draw calls are valid. +/// Tracks the state of a [`TrackedRenderPass`]. +/// +/// This is used to skip redundant operations on the [`TrackedRenderPass`] (e.g. setting an already set pipeline, binding an already bound bind group). #[derive(Debug, Default)] pub struct DrawState { pipeline: Option, @@ -20,6 +22,21 @@ pub struct DrawState { } impl DrawState { + /// Marks the `pipeline` as bound. + pub fn set_pipeline(&mut self, pipeline: RenderPipelineId) { + // TODO: do these need to be cleared? + // self.bind_groups.clear(); + // self.vertex_buffers.clear(); + // self.index_buffer = None; + self.pipeline = Some(pipeline); + } + + /// Checks, whether the `pipeline` is already bound. + pub fn is_pipeline_set(&self, pipeline: RenderPipelineId) -> bool { + self.pipeline == Some(pipeline) + } + + /// Marks the `bind_group` as bound to the `index`. pub fn set_bind_group( &mut self, index: usize, @@ -34,6 +51,7 @@ impl DrawState { self.bind_groups[index].1.extend(dynamic_indices); } + /// Checks, whether the `bind_group` is already bound to the `index`. pub fn is_bind_group_set( &self, index: usize, @@ -47,6 +65,7 @@ impl DrawState { } } + /// Marks the vertex `buffer` as bound to the `index`. pub fn set_vertex_buffer(&mut self, index: usize, buffer: BufferId, offset: u64) { if index >= self.vertex_buffers.len() { self.vertex_buffers.resize(index + 1, None); @@ -54,6 +73,7 @@ impl DrawState { self.vertex_buffers[index] = Some((buffer, offset)); } + /// Checks, whether the vertex `buffer` is already bound to the `index`. pub fn is_vertex_buffer_set(&self, index: usize, buffer: BufferId, offset: u64) -> bool { if let Some(current) = self.vertex_buffers.get(index) { *current == Some((buffer, offset)) @@ -62,10 +82,12 @@ impl DrawState { } } + /// Marks the index `buffer` as bound to the `index`. pub fn set_index_buffer(&mut self, buffer: BufferId, offset: u64, index_format: IndexFormat) { self.index_buffer = Some((buffer, offset, index_format)); } + /// Checks, whether the index `buffer` is already bound to the `index`. pub fn is_index_buffer_set( &self, buffer: BufferId, @@ -74,22 +96,10 @@ impl DrawState { ) -> bool { self.index_buffer == Some((buffer, offset, index_format)) } - - pub fn is_pipeline_set(&self, pipeline: RenderPipelineId) -> bool { - self.pipeline == Some(pipeline) - } - - pub fn set_pipeline(&mut self, pipeline: RenderPipelineId) { - // TODO: do these need to be cleared? - // self.bind_groups.clear(); - // self.vertex_buffers.clear(); - // self.index_buffer = None; - self.pipeline = Some(pipeline); - } } -/// A [`RenderPass`], which tracks the current pipeline state to ensure all draw calls are valid. -/// It is used to set the current [`RenderPipeline`], [`BindGroups`](BindGroup) and buffers. +/// A [`RenderPass`], which tracks the current pipeline state to skip redundant operations. +/// It is used to set the current [`RenderPipeline`], [`BindGroup`]s and [`Buffer`]s. /// After all requirements are specified, draw calls can be issued. pub struct TrackedRenderPass<'a> { pass: RenderPass<'a>, @@ -117,8 +127,12 @@ impl<'a> TrackedRenderPass<'a> { self.state.set_pipeline(pipeline.id()); } - /// Sets the active [`BindGroup`] for a given bind group index. The bind group layout in the - /// active pipeline when any `draw()` function is called must match the layout of this `bind group`. + /// Sets the active bind group for a given bind group index. The bind group layout + /// in the active pipeline when any `draw()` function is called must match the layout of this bind group. + /// + /// If the bind group have dynamic offsets, provide them in binding order. + /// These offsets have to be aligned to [`WgpuLimits::min_uniform_buffer_offset_alignment`](crate::settings::WgpuLimits::min_uniform_buffer_offset_alignment) + /// or [`WgpuLimits::min_storage_buffer_offset_alignment`](crate::settings::WgpuLimits::min_storage_buffer_offset_alignment) appropriately. pub fn set_bind_group( &mut self, index: usize, @@ -152,11 +166,14 @@ impl<'a> TrackedRenderPass<'a> { /// Assign a vertex buffer to a slot. /// - /// Subsequent calls to [`TrackedRenderPass::draw`] and [`TrackedRenderPass::draw_indexed`] - /// will use the buffer referenced by `buffer_slice` as one of the source vertex buffer(s). + /// Subsequent calls to [`draw`] and [`draw_indexed`] on this + /// [`RenderPass`] will use `buffer` as one of the source vertex buffers. /// - /// The `slot_index` refers to the index of the matching descriptor in + /// The `slot` refers to the index of the matching descriptor in /// [`VertexState::buffers`](crate::render_resource::VertexState::buffers). + /// + /// [`draw`]: TrackedRenderPass::draw + /// [`draw_indexed`]: TrackedRenderPass::draw_indexed pub fn set_vertex_buffer(&mut self, slot_index: usize, buffer_slice: BufferSlice<'a>) { let offset = buffer_slice.offset(); if self @@ -497,7 +514,7 @@ impl<'a> TrackedRenderPass<'a> { .set_viewport(x, y, width, height, min_depth, max_depth); } - /// Set the rendering viewport to the given [`Camera`](crate::camera::Viewport) [`Viewport`]. + /// Set the rendering viewport to the given camera [`Viewport`]. /// /// Subsequent draw calls will be projected into that viewport. pub fn set_camera_viewport(&mut self, viewport: &Viewport) { @@ -561,6 +578,9 @@ impl<'a> TrackedRenderPass<'a> { self.pass.pop_debug_group(); } + /// Sets the blend color as used by some of the blending modes. + /// + /// Subsequent blending tests will test against this value. pub fn set_blend_constant(&mut self, color: Color) { trace!("set blend constant: {:?}", color); self.pass.set_blend_constant(wgpu::Color::from(color)); diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 29564481ca454a..8169f716698b6b 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -1,12 +1,43 @@ +//! A modular rendering abstraction responsible for queuing, preparing, sorting and drawing entities +//! as part of separate phases. +//! +//! To draw an entity, a corresponding [`PhaseItem`] has to be added to one of the renderers +//! multiple [`RenderPhase`]s (e.g. opaque, transparent, shadow, etc). +//! This must be done in the [`RenderStage::Queue`](crate::RenderStage::Queue). +//! After that the [`RenderPhase`] sorts them in the +//! [`RenderStage::PhaseSort`](crate::RenderStage::PhaseSort). +//! Finally the [`PhaseItem`]s are rendered using a single [`TrackedRenderPass`], during the +//! [`RenderStage::Render`](crate::RenderStage::Render). +//! +//! Therefore each [`PhaseItem`] is assigned a [`Draw`] function. +//! These set up the state of the [`TrackedRenderPass`] (i.e. select the +//! [`RenderPipeline`](crate::render_resource::RenderPipeline), configure the +//! [`BindGroup`](crate::render_resource::BindGroup)s, etc.) and then issue a draw call, +//! for the corresponding [`PhaseItem`]. +//! +//! The [`Draw`] function trait can either be implemented directly or such a function can be +//! created by composing multiple [`RenderCommand`]s. + mod draw; mod draw_state; +mod rangefinder; pub use draw::*; pub use draw_state::*; +pub use rangefinder::*; -use bevy_ecs::prelude::{Component, Query}; +use bevy_ecs::prelude::*; +use std::ops::Range; -/// A resource to collect and sort draw requests for specific [`PhaseItems`](PhaseItem). +/// A render phase sorts and renders all [`PhaseItem`]s (entities) that are assigned to it. +/// +/// Each view (camera, or shadow-casting light, etc.) can have one or multiple render phases. +/// They are used to queue entities for rendering. +/// Multiple phases might be required due to different sorting/batching behaviours +/// (e.g. opaque: front to back, transparent: back to front) or because one phase depends on +/// the rendered texture of the previous phase (e.g. for screen-space reflections). +/// All phase items are then rendered using a single [`TrackedRenderPass`]. +/// This render pass might be reused for multiple phases. #[derive(Component)] pub struct RenderPhase { pub items: Vec, @@ -25,10 +56,27 @@ impl RenderPhase { self.items.push(item); } - /// Sorts all of its [`PhaseItems`](PhaseItem). + /// Sorts all of its [`PhaseItem`]s. pub fn sort(&mut self) { I::sort(&mut self.items); } + + /// Renders all of its [`PhaseItem`]s using their corresponding draw functions. + pub fn render<'w>( + &self, + render_pass: &mut TrackedRenderPass<'w>, + world: &'w World, + view: Entity, + ) { + let draw_functions = world.resource::>(); + let mut draw_functions = draw_functions.write(); + draw_functions.prepare(world); + + for item in &self.items { + let draw_function = draw_functions.get_mut(item.draw_function()).unwrap(); + draw_function.draw(world, render_pass, view, item); + } + } } impl RenderPhase { @@ -58,7 +106,94 @@ impl RenderPhase { } } -/// This system sorts all [`RenderPhases`](RenderPhase) for the [`PhaseItem`] type. +/// An item (entity) which will be drawn to a texture or the screen, as part of a [`RenderPhase`]. +/// +/// A phase item has to be queued up for rendering during the +/// [`RenderStage::Queue`](crate::RenderStage::Queue). +/// Afterwards it will be sorted and rendered automatically in the +/// [`RenderStage::PhaseSort`](crate::RenderStage::PhaseSort) and +/// [`RenderStage::Render`](crate::RenderStage::Render), respectively. +pub trait PhaseItem: Sized + Send + Sync + 'static { + /// The type used for ordering the items. The smallest values are drawn first. + /// This order can be calculated using the [`ViewRangefinder3d`], + /// based on the view-space `Z` value of the corresponding view matrix. + type SortKey: Ord; + + /// The corresponding entity that will be drawn. + fn entity(&self) -> Entity; + + /// Determines the order in which the items are drawn. + fn sort_key(&self) -> Self::SortKey; + + /// Specifies the [`Draw`] function used to render the item. + fn draw_function(&self) -> DrawFunctionId; + + /// Sorts a slice of phase items into render order. Generally if the same type + /// implements [`BatchedPhaseItem`], this should use a stable sort like [`slice::sort_by_key`]. + /// In almost all other cases, this should not be altered from the default, + /// which uses a unstable sort, as this provides the best balance of CPU and GPU + /// performance. + /// + /// Implementers can optionally not sort the list at all. This is generally advisable if and + /// only if the renderer supports a depth prepass, which is by default not supported by + /// the rest of Bevy's first party rendering crates. Even then, this may have a negative + /// impact on GPU-side performance due to overdraw. + /// + /// It's advised to always profile for performance changes when changing this implementation. + #[inline] + fn sort(items: &mut [Self]) { + items.sort_unstable_by_key(|item| item.sort_key()); + } +} + +/// A [`PhaseItem`] that can be batched dynamically. +/// +/// Batching is an optimization that regroups multiple items in the same vertex buffer +/// to render them in a single draw call. +/// +/// If this is implemented on a type, the implementation of [`PhaseItem::sort`] should +/// be changed to implement a stable sort, or incorrect/suboptimal batching may result. +pub trait BatchedPhaseItem: PhaseItem { + /// Range in the vertex buffer of this item + fn batch_range(&self) -> &Option>; + + /// Range in the vertex buffer of this item + fn batch_range_mut(&mut self) -> &mut Option>; + + /// Batches another item within this item if they are compatible. + /// Items can be batched together if they have the same entity, and consecutive ranges. + /// If batching is successful, the `other` item should be discarded from the render pass. + #[inline] + fn add_to_batch(&mut self, other: &Self) -> BatchResult { + let self_entity = self.entity(); + if let (Some(self_batch_range), Some(other_batch_range)) = ( + self.batch_range_mut().as_mut(), + other.batch_range().as_ref(), + ) { + // If the items are compatible, join their range into `self` + if self_entity == other.entity() { + if self_batch_range.end == other_batch_range.start { + self_batch_range.end = other_batch_range.end; + return BatchResult::Success; + } else if self_batch_range.start == other_batch_range.end { + self_batch_range.start = other_batch_range.start; + return BatchResult::Success; + } + } + } + BatchResult::IncompatibleItems + } +} + +/// The result of a batching operation. +pub enum BatchResult { + /// The `other` item was batched into `self` + Success, + /// `self` and `other` cannot be batched together + IncompatibleItems, +} + +/// This system sorts the [`PhaseItem`]s of all [`RenderPhase`]s of this type. pub fn sort_phase_system(mut render_phases: Query<&mut RenderPhase>) { for mut phase in &mut render_phases { phase.sort(); @@ -74,11 +209,9 @@ pub fn batch_phase_system(mut render_phases: Query<&mut Ren #[cfg(test)] mod tests { - use std::ops::Range; - - use bevy_ecs::entity::Entity; - use super::*; + use bevy_ecs::entity::Entity; + use std::ops::Range; #[test] fn batching() { @@ -90,23 +223,22 @@ mod tests { impl PhaseItem for TestPhaseItem { type SortKey = (); + fn entity(&self) -> Entity { + self.entity + } + fn sort_key(&self) -> Self::SortKey {} fn draw_function(&self) -> DrawFunctionId { unimplemented!(); } } - impl EntityPhaseItem for TestPhaseItem { - fn entity(&self) -> bevy_ecs::entity::Entity { - self.entity - } - } impl BatchedPhaseItem for TestPhaseItem { - fn batch_range(&self) -> &Option> { + fn batch_range(&self) -> &Option> { &self.batch_range } - fn batch_range_mut(&mut self) -> &mut Option> { + fn batch_range_mut(&mut self) -> &mut Option> { &mut self.batch_range } } diff --git a/crates/bevy_render/src/rangefinder.rs b/crates/bevy_render/src/render_phase/rangefinder.rs similarity index 89% rename from crates/bevy_render/src/rangefinder.rs rename to crates/bevy_render/src/render_phase/rangefinder.rs index c12ab3af16b11a..797782b9ccf666 100644 --- a/crates/bevy_render/src/rangefinder.rs +++ b/crates/bevy_render/src/render_phase/rangefinder.rs @@ -6,15 +6,16 @@ pub struct ViewRangefinder3d { } impl ViewRangefinder3d { - /// Creates a 3D rangefinder for a view matrix + /// Creates a 3D rangefinder for a view matrix. pub fn from_view_matrix(view_matrix: &Mat4) -> ViewRangefinder3d { let inverse_view_matrix = view_matrix.inverse(); + ViewRangefinder3d { inverse_view_row_2: inverse_view_matrix.row(2), } } - /// Calculates the distance, or view-space `Z` value, for a transform + /// Calculates the distance, or view-space `Z` value, for the given `transform`. #[inline] pub fn distance(&self, transform: &Mat4) -> f32 { // NOTE: row 2 of the inverse view matrix dotted with column 3 of the model matrix diff --git a/crates/bevy_render/src/render_resource/bind_group.rs b/crates/bevy_render/src/render_resource/bind_group.rs index c19f07605320f4..5bc13b1d1f47ad 100644 --- a/crates/bevy_render/src/render_resource/bind_group.rs +++ b/crates/bevy_render/src/render_resource/bind_group.rs @@ -1,24 +1,19 @@ -pub use bevy_render_macros::AsBindGroup; -use encase::ShaderType; - use crate::{ + define_atomic_id, prelude::Image, render_asset::RenderAssets, - render_resource::{BindGroupLayout, Buffer, Sampler, TextureView}, + render_resource::{resource_macros::*, BindGroupLayout, Buffer, Sampler, TextureView}, renderer::RenderDevice, texture::FallbackImage, }; -use bevy_reflect::Uuid; +pub use bevy_render_macros::AsBindGroup; +use encase::ShaderType; use std::ops::Deref; use wgpu::BindingResource; -use crate::render_resource::resource_macros::*; +define_atomic_id!(BindGroupId); render_resource_wrapper!(ErasedBindGroup, wgpu::BindGroup); -/// A [`BindGroup`] identifier. -#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] -pub struct BindGroupId(Uuid); - /// Bind groups are responsible for binding render resources (e.g. buffers, textures, samplers) /// to a [`TrackedRenderPass`](crate::render_phase::TrackedRenderPass). /// This makes them accessible in the pipeline (shaders) as uniforms. @@ -42,7 +37,7 @@ impl BindGroup { impl From for BindGroup { fn from(value: wgpu::BindGroup) -> Self { BindGroup { - id: BindGroupId(Uuid::new_v4()), + id: BindGroupId::new(), value: ErasedBindGroup::new(value), } } @@ -253,7 +248,7 @@ impl Deref for BindGroup { /// } /// } /// ``` -pub trait AsBindGroup: Sized { +pub trait AsBindGroup { /// Data that will be stored alongside the "prepared" bind group. type Data: Send + Sync; @@ -264,10 +259,12 @@ pub trait AsBindGroup: Sized { render_device: &RenderDevice, images: &RenderAssets, fallback_image: &FallbackImage, - ) -> Result, AsBindGroupError>; + ) -> Result, AsBindGroupError>; /// Creates the bind group layout matching all bind groups returned by [`AsBindGroup::as_bind_group`] - fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout; + fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout + where + Self: Sized; } /// An error that occurs during [`AsBindGroup::as_bind_group`] calls. @@ -277,10 +274,10 @@ pub enum AsBindGroupError { } /// A prepared bind group returned as a result of [`AsBindGroup::as_bind_group`]. -pub struct PreparedBindGroup { +pub struct PreparedBindGroup { pub bindings: Vec, pub bind_group: BindGroup, - pub data: T::Data, + pub data: T, } /// An owned binding resource of any type (ex: a [`Buffer`], [`TextureView`], etc). diff --git a/crates/bevy_render/src/render_resource/bind_group_layout.rs b/crates/bevy_render/src/render_resource/bind_group_layout.rs index 47edbbd113580c..9793f1391d1883 100644 --- a/crates/bevy_render/src/render_resource/bind_group_layout.rs +++ b/crates/bevy_render/src/render_resource/bind_group_layout.rs @@ -1,10 +1,7 @@ -use crate::render_resource::resource_macros::*; -use bevy_reflect::Uuid; +use crate::{define_atomic_id, render_resource::resource_macros::*}; use std::ops::Deref; -#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] -pub struct BindGroupLayoutId(Uuid); - +define_atomic_id!(BindGroupLayoutId); render_resource_wrapper!(ErasedBindGroupLayout, wgpu::BindGroupLayout); #[derive(Clone, Debug)] @@ -34,7 +31,7 @@ impl BindGroupLayout { impl From for BindGroupLayout { fn from(value: wgpu::BindGroupLayout) -> Self { BindGroupLayout { - id: BindGroupLayoutId(Uuid::new_v4()), + id: BindGroupLayoutId::new(), value: ErasedBindGroupLayout::new(value), } } diff --git a/crates/bevy_render/src/render_resource/buffer.rs b/crates/bevy_render/src/render_resource/buffer.rs index 6bae2e529ab417..9867945673efba 100644 --- a/crates/bevy_render/src/render_resource/buffer.rs +++ b/crates/bevy_render/src/render_resource/buffer.rs @@ -1,11 +1,7 @@ -use bevy_utils::Uuid; +use crate::{define_atomic_id, render_resource::resource_macros::render_resource_wrapper}; use std::ops::{Bound, Deref, RangeBounds}; -use crate::render_resource::resource_macros::*; - -#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] -pub struct BufferId(Uuid); - +define_atomic_id!(BufferId); render_resource_wrapper!(ErasedBuffer, wgpu::Buffer); #[derive(Clone, Debug)] @@ -42,7 +38,7 @@ impl Buffer { impl From for Buffer { fn from(value: wgpu::Buffer) -> Self { Buffer { - id: BufferId(Uuid::new_v4()), + id: BufferId::new(), value: ErasedBuffer::new(value), } } diff --git a/crates/bevy_render/src/render_resource/buffer_vec.rs b/crates/bevy_render/src/render_resource/buffer_vec.rs index 1d3086796dbc5c..4a3c744e071c98 100644 --- a/crates/bevy_render/src/render_resource/buffer_vec.rs +++ b/crates/bevy_render/src/render_resource/buffer_vec.rs @@ -131,7 +131,18 @@ impl BufferVec { } } + pub fn truncate(&mut self, len: usize) { + self.values.truncate(len); + } + pub fn clear(&mut self) { self.values.clear(); } } + +impl Extend for BufferVec { + #[inline] + fn extend>(&mut self, iter: I) { + self.values.extend(iter); + } +} diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index 3a379b60522f22..edd3d8f6d67cbf 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -1,19 +1,16 @@ -use crate::render_resource::{BindGroupLayout, Shader}; +use super::ShaderDefVal; +use crate::{ + define_atomic_id, + render_resource::{resource_macros::render_resource_wrapper, BindGroupLayout, Shader}, +}; use bevy_asset::Handle; -use bevy_reflect::Uuid; use std::{borrow::Cow, ops::Deref}; use wgpu::{ BufferAddress, ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState, VertexAttribute, VertexFormat, VertexStepMode, }; -use super::ShaderDefVal; -use crate::render_resource::resource_macros::*; - -/// A [`RenderPipeline`] identifier. -#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] -pub struct RenderPipelineId(Uuid); - +define_atomic_id!(RenderPipelineId); render_resource_wrapper!(ErasedRenderPipeline, wgpu::RenderPipeline); /// A [`RenderPipeline`] represents a graphics pipeline and its stages (shaders), bindings and vertex buffers. @@ -36,7 +33,7 @@ impl RenderPipeline { impl From for RenderPipeline { fn from(value: wgpu::RenderPipeline) -> Self { RenderPipeline { - id: RenderPipelineId(Uuid::new_v4()), + id: RenderPipelineId::new(), value: ErasedRenderPipeline::new(value), } } @@ -51,10 +48,7 @@ impl Deref for RenderPipeline { } } -/// A [`ComputePipeline`] identifier. -#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] -pub struct ComputePipelineId(Uuid); - +define_atomic_id!(ComputePipelineId); render_resource_wrapper!(ErasedComputePipeline, wgpu::ComputePipeline); /// A [`ComputePipeline`] represents a compute pipeline and its single shader stage. @@ -78,7 +72,7 @@ impl ComputePipeline { impl From for ComputePipeline { fn from(value: wgpu::ComputePipeline) -> Self { ComputePipeline { - id: ComputePipelineId(Uuid::new_v4()), + id: ComputePipelineId::new(), value: ErasedComputePipeline::new(value), } } diff --git a/crates/bevy_render/src/render_resource/resource_macros.rs b/crates/bevy_render/src/render_resource/resource_macros.rs index 4f1a69673b043f..e27380426a169b 100644 --- a/crates/bevy_render/src/render_resource/resource_macros.rs +++ b/crates/bevy_render/src/render_resource/resource_macros.rs @@ -120,4 +120,32 @@ macro_rules! render_resource_wrapper { }; } +#[macro_export] +macro_rules! define_atomic_id { + ($atomic_id_type:ident) => { + #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] + pub struct $atomic_id_type(u32); + + // We use new instead of default to indicate that each ID created will be unique. + #[allow(clippy::new_without_default)] + impl $atomic_id_type { + pub fn new() -> Self { + use std::sync::atomic::{AtomicU32, Ordering}; + + static COUNTER: AtomicU32 = AtomicU32::new(1); + + match COUNTER.fetch_add(1, Ordering::Relaxed) { + 0 => { + panic!( + "The system ran out of unique `{}`s.", + stringify!($atomic_id_type) + ); + } + id => Self(id), + } + } + } + }; +} + pub use render_resource_wrapper; diff --git a/crates/bevy_render/src/render_resource/shader.rs b/crates/bevy_render/src/render_resource/shader.rs index 569234abeeb6db..08f01f64fa81c9 100644 --- a/crates/bevy_render/src/render_resource/shader.rs +++ b/crates/bevy_render/src/render_resource/shader.rs @@ -1,27 +1,16 @@ +use super::ShaderDefVal; +use crate::define_atomic_id; use bevy_asset::{AssetLoader, AssetPath, Handle, LoadContext, LoadedAsset}; -use bevy_reflect::{TypeUuid, Uuid}; +use bevy_reflect::TypeUuid; use bevy_utils::{tracing::error, BoxedFuture, HashMap}; -use naga::back::wgsl::WriterFlags; -use naga::valid::Capabilities; -use naga::{valid::ModuleInfo, Module}; +use naga::{back::wgsl::WriterFlags, valid::Capabilities, valid::ModuleInfo, Module}; use once_cell::sync::Lazy; use regex::Regex; use std::{borrow::Cow, marker::Copy, ops::Deref, path::PathBuf, str::FromStr}; use thiserror::Error; -use wgpu::Features; -use wgpu::{util::make_spirv, ShaderModuleDescriptor, ShaderSource}; - -use super::ShaderDefVal; +use wgpu::{util::make_spirv, Features, ShaderModuleDescriptor, ShaderSource}; -#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] -pub struct ShaderId(Uuid); - -impl ShaderId { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - ShaderId(Uuid::new_v4()) - } -} +define_atomic_id!(ShaderId); #[derive(Error, Debug)] pub enum ShaderReflectError { diff --git a/crates/bevy_render/src/render_resource/texture.rs b/crates/bevy_render/src/render_resource/texture.rs index 330d7cdb0b85e8..02cc8184080734 100644 --- a/crates/bevy_render/src/render_resource/texture.rs +++ b/crates/bevy_render/src/render_resource/texture.rs @@ -1,12 +1,9 @@ -use bevy_utils::Uuid; +use crate::define_atomic_id; use std::ops::Deref; use crate::render_resource::resource_macros::*; -/// A [`Texture`] identifier. -#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] -pub struct TextureId(Uuid); - +define_atomic_id!(TextureId); render_resource_wrapper!(ErasedTexture, wgpu::Texture); /// A GPU-accessible texture. @@ -35,7 +32,7 @@ impl Texture { impl From for Texture { fn from(value: wgpu::Texture) -> Self { Texture { - id: TextureId(Uuid::new_v4()), + id: TextureId::new(), value: ErasedTexture::new(value), } } @@ -50,10 +47,7 @@ impl Deref for Texture { } } -/// A [`TextureView`] identifier. -#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] -pub struct TextureViewId(Uuid); - +define_atomic_id!(TextureViewId); render_resource_wrapper!(ErasedTextureView, wgpu::TextureView); render_resource_wrapper!(ErasedSurfaceTexture, wgpu::SurfaceTexture); @@ -104,7 +98,7 @@ impl TextureView { impl From for TextureView { fn from(value: wgpu::TextureView) -> Self { TextureView { - id: TextureViewId(Uuid::new_v4()), + id: TextureViewId::new(), value: TextureViewValue::TextureView(ErasedTextureView::new(value)), } } @@ -116,7 +110,7 @@ impl From for TextureView { let texture = ErasedSurfaceTexture::new(value); TextureView { - id: TextureViewId(Uuid::new_v4()), + id: TextureViewId::new(), value: TextureViewValue::SurfaceTexture { texture, view }, } } @@ -134,10 +128,7 @@ impl Deref for TextureView { } } -/// A [`Sampler`] identifier. -#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] -pub struct SamplerId(Uuid); - +define_atomic_id!(SamplerId); render_resource_wrapper!(ErasedSampler, wgpu::Sampler); /// A Sampler defines how a pipeline will sample from a [`TextureView`]. @@ -162,7 +153,7 @@ impl Sampler { impl From for Sampler { fn from(value: wgpu::Sampler) -> Self { Sampler { - id: SamplerId(Uuid::new_v4()), + id: SamplerId::new(), value: ErasedSampler::new(value), } } diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index 6f697ca7d0594c..aa362a3d9b2edd 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -137,7 +137,7 @@ pub async fn initialize_renderer( let mut features = wgpu::Features::empty(); let mut limits = options.limits.clone(); if matches!(options.priority, WgpuSettingsPriority::Functionality) { - features = adapter.features() | wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES; + features = adapter.features(); if adapter_info.device_type == wgpu::DeviceType::DiscreteGpu { // `MAPPABLE_PRIMARY_BUFFERS` can have a significant, negative performance impact for // discrete GPUs due to having to transfer data across the PCI-E bus and so it diff --git a/crates/bevy_render/src/settings.rs b/crates/bevy_render/src/settings.rs index 52c15fad7720bd..62bdc7be43ba25 100644 --- a/crates/bevy_render/src/settings.rs +++ b/crates/bevy_render/src/settings.rs @@ -1,6 +1,5 @@ use std::borrow::Cow; -use bevy_ecs::system::Resource; pub use wgpu::{Backends, Features as WgpuFeatures, Limits as WgpuLimits, PowerPreference}; /// Configures the priority used when automatically configuring the features/limits of `wgpu`. @@ -15,15 +14,15 @@ pub enum WgpuSettingsPriority { } /// Provides configuration for renderer initialization. Use [`RenderDevice::features`](crate::renderer::RenderDevice::features), -/// [`RenderDevice::limits`](crate::renderer::RenderDevice::limits), and the [`WgpuAdapterInfo`](crate::render_resource::WgpuAdapterInfo) +/// [`RenderDevice::limits`](crate::renderer::RenderDevice::limits), and the [`RenderAdapterInfo`](crate::renderer::RenderAdapterInfo) /// resource to get runtime information about the actual adapter, backend, features, and limits. /// NOTE: [`Backends::DX12`](Backends::DX12), [`Backends::METAL`](Backends::METAL), and /// [`Backends::VULKAN`](Backends::VULKAN) are enabled by default for non-web and the best choice /// is automatically selected. Web using the `webgl` feature uses [`Backends::GL`](Backends::GL). -/// NOTE: If you want to use [`Backends::GL`](Backends::GL) in a native app on Windows, you must +/// NOTE: If you want to use [`Backends::GL`](Backends::GL) in a native app on `Windows` and/or `macOS`, you must /// use [`ANGLE`](https://github.com/gfx-rs/wgpu#angle). This is because wgpu requires EGL to /// create a GL context without a window and only ANGLE supports that. -#[derive(Resource, Clone)] +#[derive(Clone)] pub struct WgpuSettings { pub device_label: Option>, pub backends: Option, diff --git a/crates/bevy_render/src/spatial_bundle.rs b/crates/bevy_render/src/spatial_bundle.rs index 6eedd7cbf59924..b2d50da6097f6c 100644 --- a/crates/bevy_render/src/spatial_bundle.rs +++ b/crates/bevy_render/src/spatial_bundle.rs @@ -33,22 +33,22 @@ impl SpatialBundle { pub const fn from_transform(transform: Transform) -> Self { SpatialBundle { transform, - ..Self::VISIBLE_IDENTITY + ..Self::INHERITED_IDENTITY } } /// A visible [`SpatialBundle`], with no translation, rotation, and a scale of 1 on all axes. - pub const VISIBLE_IDENTITY: Self = SpatialBundle { - visibility: Visibility::VISIBLE, - computed: ComputedVisibility::INVISIBLE, + pub const INHERITED_IDENTITY: Self = SpatialBundle { + visibility: Visibility::Inherited, + computed: ComputedVisibility::HIDDEN, transform: Transform::IDENTITY, global_transform: GlobalTransform::IDENTITY, }; /// An invisible [`SpatialBundle`], with no translation, rotation, and a scale of 1 on all axes. - pub const INVISIBLE_IDENTITY: Self = SpatialBundle { - visibility: Visibility::INVISIBLE, - ..Self::VISIBLE_IDENTITY + pub const HIDDEN_IDENTITY: Self = SpatialBundle { + visibility: Visibility::Hidden, + ..Self::INHERITED_IDENTITY }; } diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index bc0c12b0231358..3bb6479c86e898 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -8,8 +8,8 @@ use crate::{ camera::ExtractedCamera, extract_resource::{ExtractResource, ExtractResourcePlugin}, prelude::Image, - rangefinder::ViewRangefinder3d, render_asset::RenderAssets, + render_phase::ViewRangefinder3d, render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView}, renderer::{RenderDevice, RenderQueue}, texture::{BevyDefault, TextureCache}, diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index cfa85157a62703..e4a57217c1bc1d 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -23,34 +23,42 @@ use crate::{ }; /// User indication of whether an entity is visible. Propagates down the entity hierarchy. - -/// If an entity is hidden in this way, all [`Children`] (and all of their children and so on) will also be hidden. -/// This is done by setting the values of their [`ComputedVisibility`] component. -#[derive(Component, Clone, Reflect, FromReflect, Debug)] +/// +/// If an entity is hidden in this way, all [`Children`] (and all of their children and so on) who +/// are set to `Inherited` will also be hidden. +/// +/// This is done by the `visibility_propagate_system` which uses the entity hierarchy and +/// `Visibility` to set the values of each entity's [`ComputedVisibility`] component. +#[derive(Component, Clone, Copy, Reflect, FromReflect, Debug, PartialEq, Eq, Default)] #[reflect(Component, Default)] -pub struct Visibility { - /// Indicates whether this entity is visible. Hidden values will propagate down the entity hierarchy. - /// If this entity is hidden, all of its descendants will be hidden as well. See [`Children`] and [`Parent`] for - /// hierarchy info. - pub is_visible: bool, +pub enum Visibility { + /// An entity with `Visibility::Inherited` will inherit the Visibility of its [`Parent`]. + /// + /// A root-level entity that is set to `Inherited` will be visible. + #[default] + Inherited, + /// An entity with `Visibility::Hidden` will be unconditionally hidden. + Hidden, + /// An entity with `Visibility::Visible` will be unconditionally visible. + /// + /// Note that an entity with `Visibility::Visible` will be visible regardless of whether the + /// [`Parent`] entity is hidden. + Visible, } -impl Default for Visibility { - fn default() -> Self { - Visibility::VISIBLE +// Allows `&Visibility == Visibility` +impl std::cmp::PartialEq for &Visibility { + #[inline] + fn eq(&self, other: &Visibility) -> bool { + **self == *other } } -impl Visibility { - /// A [`Visibility`], set as visible. - pub const VISIBLE: Self = Visibility { is_visible: true }; - - /// A [`Visibility`], set as invisible. - pub const INVISIBLE: Self = Visibility { is_visible: false }; - - /// Toggle the visibility. - pub fn toggle(&mut self) { - self.is_visible = !self.is_visible; +// Allows `Visibility == &Visibility` +impl std::cmp::PartialEq<&Visibility> for Visibility { + #[inline] + fn eq(&self, other: &&Visibility) -> bool { + *self == **other } } @@ -71,13 +79,13 @@ pub struct ComputedVisibility { impl Default for ComputedVisibility { fn default() -> Self { - Self::INVISIBLE + Self::HIDDEN } } impl ComputedVisibility { /// A [`ComputedVisibility`], set as invisible. - pub const INVISIBLE: Self = ComputedVisibility { + pub const HIDDEN: Self = ComputedVisibility { flags: ComputedVisibilityFlags::empty(), }; @@ -298,7 +306,8 @@ fn visibility_propagate_system( ) { for (children, visibility, mut computed_visibility, entity) in root_query.iter_mut() { // reset "view" visibility here ... if this entity should be drawn a future system should set this to true - computed_visibility.reset(visibility.is_visible); + computed_visibility + .reset(visibility == Visibility::Inherited || visibility == Visibility::Visible); if let Some(children) = children { for child in children.iter() { let _ = propagate_recursive( @@ -329,7 +338,8 @@ fn propagate_recursive( child_parent.get(), expected_parent, "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle" ); - let visible_in_hierarchy = visibility.is_visible && parent_visible; + let visible_in_hierarchy = (parent_visible && visibility == Visibility::Inherited) + || visibility == Visibility::Visible; // reset "view" visibility here ... if this entity should be drawn a future system should set this to true computed_visibility.reset(visible_in_hierarchy); visible_in_hierarchy @@ -458,10 +468,7 @@ mod test { let root1 = app .world - .spawn(( - Visibility { is_visible: false }, - ComputedVisibility::default(), - )) + .spawn((Visibility::Hidden, ComputedVisibility::default())) .id(); let root1_child1 = app .world @@ -469,10 +476,7 @@ mod test { .id(); let root1_child2 = app .world - .spawn(( - Visibility { is_visible: false }, - ComputedVisibility::default(), - )) + .spawn((Visibility::Hidden, ComputedVisibility::default())) .id(); let root1_child1_grandchild1 = app .world @@ -503,10 +507,7 @@ mod test { .id(); let root2_child2 = app .world - .spawn(( - Visibility { is_visible: false }, - ComputedVisibility::default(), - )) + .spawn((Visibility::Hidden, ComputedVisibility::default())) .id(); let root2_child1_grandchild1 = app .world @@ -578,4 +579,89 @@ mod test { "child's invisibility propagates down to grandchild" ); } + + #[test] + fn visibility_propagation_unconditional_visible() { + let mut app = App::new(); + app.add_system(visibility_propagate_system); + + let root1 = app + .world + .spawn((Visibility::Visible, ComputedVisibility::default())) + .id(); + let root1_child1 = app + .world + .spawn((Visibility::Inherited, ComputedVisibility::default())) + .id(); + let root1_child2 = app + .world + .spawn((Visibility::Hidden, ComputedVisibility::default())) + .id(); + let root1_child1_grandchild1 = app + .world + .spawn((Visibility::Visible, ComputedVisibility::default())) + .id(); + let root1_child2_grandchild1 = app + .world + .spawn((Visibility::Visible, ComputedVisibility::default())) + .id(); + + let root2 = app + .world + .spawn((Visibility::Inherited, ComputedVisibility::default())) + .id(); + let root3 = app + .world + .spawn((Visibility::Hidden, ComputedVisibility::default())) + .id(); + + app.world + .entity_mut(root1) + .push_children(&[root1_child1, root1_child2]); + app.world + .entity_mut(root1_child1) + .push_children(&[root1_child1_grandchild1]); + app.world + .entity_mut(root1_child2) + .push_children(&[root1_child2_grandchild1]); + + app.update(); + + let is_visible = |e: Entity| { + app.world + .entity(e) + .get::() + .unwrap() + .is_visible_in_hierarchy() + }; + assert!( + is_visible(root1), + "an unconditionally visible root is visible" + ); + assert!( + is_visible(root1_child1), + "an inheriting child of an unconditionally visible parent is visible" + ); + assert!( + !is_visible(root1_child2), + "a hidden child on an unconditionally visible parent is hidden" + ); + assert!( + is_visible(root1_child1_grandchild1), + "an unconditionally visible child of an inheriting parent is visible" + ); + assert!( + is_visible(root1_child2_grandchild1), + "an unconditionally visible child of a hidden parent is visible" + ); + assert!(is_visible(root2), "an inheriting root is visible"); + assert!(!is_visible(root3), "a hidden root is hidden"); + } + + #[test] + fn ensure_visibility_enum_size() { + use std::mem; + assert_eq!(1, mem::size_of::()); + assert_eq!(1, mem::size_of::>()); + } } diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 841af6756f3ba3..5ea65a8714eab7 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -31,9 +31,9 @@ use std::collections::BTreeMap; /// let dynamic_scene = builder.build(); /// ``` pub struct DynamicSceneBuilder<'w> { - entities: BTreeMap, + extracted_scene: BTreeMap, type_registry: AppTypeRegistry, - world: &'w World, + original_world: &'w World, } impl<'w> DynamicSceneBuilder<'w> { @@ -41,9 +41,9 @@ 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 { - entities: default(), + extracted_scene: default(), type_registry: world.resource::().clone(), - world, + original_world: world, } } @@ -51,16 +51,19 @@ 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 { - entities: default(), + extracted_scene: default(), type_registry, - world, + original_world: world, } } /// Consume the builder, producing a [`DynamicScene`]. + /// + /// To make sure the dynamic scene doesn't contain entities without any components, call + /// [`Self::remove_empty_entities`] before building the scene. pub fn build(self) -> DynamicScene { DynamicScene { - entities: self.entities.into_values().collect(), + entities: self.extracted_scene.into_values().collect(), } } @@ -71,6 +74,16 @@ impl<'w> DynamicSceneBuilder<'w> { self.extract_entities(std::iter::once(entity)) } + /// Despawns all enitities with no components. + /// + /// These were likely created because none of their components were present in the provided type registry upon extraction. + pub fn remove_empty_entities(&mut self) -> &mut Self { + self.extracted_scene + .retain(|_, entity| !entity.components.is_empty()); + + self + } + /// Extract entities from the builder's [`World`]. /// /// Re-extracting an entity that was already extracted will have no effect. @@ -102,7 +115,7 @@ impl<'w> DynamicSceneBuilder<'w> { for entity in entities { let index = entity.index(); - if self.entities.contains_key(&index) { + if self.extracted_scene.contains_key(&index) { continue; } @@ -111,21 +124,22 @@ impl<'w> DynamicSceneBuilder<'w> { components: Vec::new(), }; - for component_id in self.world.entity(entity).archetype().components() { + for component_id in self.original_world.entity(entity).archetype().components() { let reflect_component = self - .world + .original_world .components() .get_info(component_id) .and_then(|info| type_registry.get(info.type_id().unwrap())) .and_then(|registration| registration.data::()); if let Some(reflect_component) = reflect_component { - if let Some(component) = reflect_component.reflect(self.world, entity) { + if let Some(component) = reflect_component.reflect(self.original_world, entity) + { entry.components.push(component.clone_value()); } } } - self.entities.insert(index, entry); + self.extracted_scene.insert(index, entry); } drop(type_registry); @@ -270,4 +284,24 @@ mod tests { scene_entities.sort(); assert_eq!(scene_entities, [entity_a_b.index(), entity_a.index()]); } + + #[test] + fn remove_componentless_entity() { + let mut world = World::default(); + + let atr = AppTypeRegistry::default(); + atr.write().register::(); + world.insert_resource(atr); + + let entity_a = world.spawn(ComponentA).id(); + let entity_b = world.spawn(ComponentB).id(); + + let mut builder = DynamicSceneBuilder::from_world(&world); + builder.extract_entities([entity_a, entity_b].into_iter()); + builder.remove_empty_entities(); + let scene = builder.build(); + + assert_eq!(scene.entities.len(), 1); + assert_eq!(scene.entities[0].entity, entity_a.index()); + } } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 0f3b86bf9e1d51..0a2a0b35ee1161 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -3,12 +3,12 @@ use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle}; use bevy_core_pipeline::{core_2d::Transparent2d, tonemapping::Tonemapping}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - entity::Entity, event::EventReader, prelude::{Bundle, World}, + query::ROQueryItem, schedule::IntoSystemDescriptor, system::{ - lifetimeless::{Read, SQuery, SRes}, + lifetimeless::{Read, SRes}, Commands, Local, Query, Res, ResMut, Resource, SystemParamItem, }, world::FromWorld, @@ -21,8 +21,8 @@ use bevy_render::{ prelude::Image, render_asset::{PrepareAssetLabel, RenderAssets}, render_phase::{ - AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase, - SetItemPipeline, TrackedRenderPass, + AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, + RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::{ AsBindGroup, AsBindGroupError, BindGroup, BindGroupLayout, OwnedBindingResource, @@ -281,15 +281,21 @@ type DrawMaterial2d = ( ); pub struct SetMaterial2dBindGroup(PhantomData); -impl EntityRenderCommand for SetMaterial2dBindGroup { - type Param = (SRes>, SQuery>>); +impl RenderCommand

+ for SetMaterial2dBindGroup +{ + type Param = SRes>; + type ViewWorldQuery = (); + type ItemWorldQuery = Read>; + + #[inline] fn render<'w>( - _view: Entity, - item: Entity, - (materials, query): SystemParamItem<'w, '_, Self::Param>, + _item: &P, + _view: (), + material2d_handle: ROQueryItem<'_, Self::ItemWorldQuery>, + materials: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let material2d_handle = query.get(item).unwrap(); let material2d = materials.into_inner().get(material2d_handle).unwrap(); pass.set_bind_group(I, &material2d.bind_group, &[]); RenderCommandResult::Success diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 0910463f26c9fe..03e9f468aaa21a 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -2,6 +2,7 @@ use bevy_app::Plugin; use bevy_asset::{load_internal_asset, Handle, HandleUntyped}; use bevy_ecs::{ prelude::*, + query::ROQueryItem, system::{lifetimeless::*, SystemParamItem, SystemState}, }; use bevy_math::{Mat4, Vec2}; @@ -11,7 +12,7 @@ use bevy_render::{ globals::{GlobalsBuffer, GlobalsUniform}, mesh::{GpuBufferInfo, Mesh, MeshVertexBufferLayout}, render_asset::RenderAssets, - render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass}, + render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::{ @@ -495,16 +496,19 @@ pub fn queue_mesh2d_view_bind_groups( } pub struct SetMesh2dViewBindGroup; -impl EntityRenderCommand for SetMesh2dViewBindGroup { - type Param = SQuery<(Read, Read)>; +impl RenderCommand

for SetMesh2dViewBindGroup { + type Param = (); + type ViewWorldQuery = (Read, Read); + type ItemWorldQuery = (); + #[inline] fn render<'w>( - view: Entity, - _item: Entity, - view_query: SystemParamItem<'w, '_, Self::Param>, + _item: &P, + (view_uniform, mesh2d_view_bind_group): ROQueryItem<'w, Self::ViewWorldQuery>, + _view: (), + _param: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let (view_uniform, mesh2d_view_bind_group) = view_query.get_inner(view).unwrap(); pass.set_bind_group(I, &mesh2d_view_bind_group.value, &[view_uniform.offset]); RenderCommandResult::Success @@ -512,19 +516,19 @@ impl EntityRenderCommand for SetMesh2dViewBindGroup { } pub struct SetMesh2dBindGroup; -impl EntityRenderCommand for SetMesh2dBindGroup { - type Param = ( - SRes, - SQuery>>, - ); +impl RenderCommand

for SetMesh2dBindGroup { + type Param = SRes; + type ViewWorldQuery = (); + type ItemWorldQuery = Read>; + #[inline] fn render<'w>( - _view: Entity, - item: Entity, - (mesh2d_bind_group, mesh2d_query): SystemParamItem<'w, '_, Self::Param>, + _item: &P, + _view: (), + mesh2d_index: &'_ DynamicUniformIndex, + mesh2d_bind_group: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let mesh2d_index = mesh2d_query.get(item).unwrap(); pass.set_bind_group( I, &mesh2d_bind_group.into_inner().value, @@ -535,17 +539,20 @@ impl EntityRenderCommand for SetMesh2dBindGroup { } pub struct DrawMesh2d; -impl EntityRenderCommand for DrawMesh2d { - type Param = (SRes>, SQuery>); +impl RenderCommand

for DrawMesh2d { + type Param = SRes>; + type ViewWorldQuery = (); + type ItemWorldQuery = Read; + #[inline] fn render<'w>( - _view: Entity, - item: Entity, - (meshes, mesh2d_query): SystemParamItem<'w, '_, Self::Param>, + _item: &P, + _view: (), + mesh_handle: ROQueryItem<'w, Self::ItemWorldQuery>, + meshes: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let mesh_handle = &mesh2d_query.get(item).unwrap().0; - if let Some(gpu_mesh) = meshes.into_inner().get(mesh_handle) { + if let Some(gpu_mesh) = meshes.into_inner().get(&mesh_handle.0) { pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); match &gpu_mesh.buffer_info { GpuBufferInfo::Indexed { diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 1cdc6d93782d8b..e7df0c1c323ca4 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -16,7 +16,7 @@ use bevy_render::{ color::Color, render_asset::RenderAssets, render_phase::{ - BatchedPhaseItem, DrawFunctions, EntityRenderCommand, RenderCommand, RenderCommandResult, + BatchedPhaseItem, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::*, @@ -160,17 +160,20 @@ impl SpritePipelineKey { const MSAA_MASK_BITS: u32 = 0b111; const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones(); - pub fn from_msaa_samples(msaa_samples: u32) -> Self { + #[inline] + pub const fn from_msaa_samples(msaa_samples: u32) -> Self { let msaa_bits = (msaa_samples.trailing_zeros() & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS; - Self::from_bits(msaa_bits).unwrap() + Self::from_bits_truncate(msaa_bits) } - pub fn msaa_samples(&self) -> u32 { + #[inline] + pub const fn msaa_samples(&self) -> u32 { 1 << ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) } - pub fn from_colored(colored: bool) -> Self { + #[inline] + pub const fn from_colored(colored: bool) -> Self { if colored { SpritePipelineKey::COLORED } else { @@ -178,7 +181,8 @@ impl SpritePipelineKey { } } - pub fn from_hdr(hdr: bool) -> Self { + #[inline] + pub const fn from_hdr(hdr: bool) -> Self { if hdr { SpritePipelineKey::HDR } else { @@ -692,16 +696,18 @@ pub type DrawSprite = ( ); pub struct SetSpriteViewBindGroup; -impl EntityRenderCommand for SetSpriteViewBindGroup { - type Param = (SRes, SQuery>); +impl RenderCommand

for SetSpriteViewBindGroup { + type Param = SRes; + type ViewWorldQuery = Read; + type ItemWorldQuery = (); fn render<'w>( - view: Entity, - _item: Entity, - (sprite_meta, view_query): SystemParamItem<'w, '_, Self::Param>, + _item: &P, + view_uniform: &'_ ViewUniformOffset, + _entity: (), + sprite_meta: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let view_uniform = view_query.get(view).unwrap(); pass.set_bind_group( I, sprite_meta.into_inner().view_bind_group.as_ref().unwrap(), @@ -711,16 +717,18 @@ impl EntityRenderCommand for SetSpriteViewBindGroup { } } pub struct SetSpriteTextureBindGroup; -impl EntityRenderCommand for SetSpriteTextureBindGroup { - type Param = (SRes, SQuery>); +impl RenderCommand

for SetSpriteTextureBindGroup { + type Param = SRes; + type ViewWorldQuery = (); + type ItemWorldQuery = Read; fn render<'w>( - _view: Entity, - item: Entity, - (image_bind_groups, query_batch): SystemParamItem<'w, '_, Self::Param>, + _item: &P, + _view: (), + sprite_batch: &'_ SpriteBatch, + image_bind_groups: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let sprite_batch = query_batch.get(item).unwrap(); let image_bind_groups = image_bind_groups.into_inner(); pass.set_bind_group( @@ -737,15 +745,17 @@ impl EntityRenderCommand for SetSpriteTextureBindGroup { pub struct DrawSpriteBatch; impl RenderCommand

for DrawSpriteBatch { - type Param = (SRes, SQuery>); + type Param = SRes; + type ViewWorldQuery = (); + type ItemWorldQuery = Read; fn render<'w>( - _view: Entity, item: &P, - (sprite_meta, query_batch): SystemParamItem<'w, '_, Self::Param>, + _view: (), + sprite_batch: &'_ SpriteBatch, + sprite_meta: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let sprite_batch = query_batch.get(item.entity()).unwrap(); let sprite_meta = sprite_meta.into_inner(); if sprite_batch.colored { pass.set_vertex_buffer(0, sprite_meta.colored_vertices.buffer().unwrap().slice(..)); diff --git a/crates/bevy_tasks/src/task_pool.rs b/crates/bevy_tasks/src/task_pool.rs index 099f96e93d006e..83bc92843f00d2 100644 --- a/crates/bevy_tasks/src/task_pool.rs +++ b/crates/bevy_tasks/src/task_pool.rs @@ -12,8 +12,18 @@ use futures_lite::{future, pin, FutureExt}; use crate::Task; +struct CallOnDrop(Option>); + +impl Drop for CallOnDrop { + fn drop(&mut self) { + if let Some(call) = self.0.as_ref() { + call(); + } + } +} + /// Used to create a [`TaskPool`] -#[derive(Debug, Default, Clone)] +#[derive(Default)] #[must_use] pub struct TaskPoolBuilder { /// If set, we'll set up the thread pool to use at most `num_threads` threads. @@ -24,6 +34,9 @@ pub struct TaskPoolBuilder { /// Allows customizing the name of the threads - helpful for debugging. If set, threads will /// be named (), i.e. "MyThreadPool (2)" thread_name: Option, + + on_thread_spawn: Option>, + on_thread_destroy: Option>, } impl TaskPoolBuilder { @@ -52,13 +65,27 @@ impl TaskPoolBuilder { self } + /// Sets a callback that is invoked once for every created thread as it starts. + /// + /// This is called on the thread itself and has access to all thread-local storage. + /// This will block running async tasks on the thread until the callback completes. + pub fn on_thread_spawn(mut self, f: impl Fn() + Send + Sync + 'static) -> Self { + self.on_thread_spawn = Some(Arc::new(f)); + self + } + + /// Sets a callback that is invoked once for every created thread as it terminates. + /// + /// This is called on the thread itself and has access to all thread-local storage. + /// This will block thread termination until the callback completes. + pub fn on_thread_destroy(mut self, f: impl Fn() + Send + Sync + 'static) -> Self { + self.on_thread_destroy = Some(Arc::new(f)); + self + } + /// Creates a new [`TaskPool`] based on the current options. pub fn build(self) -> TaskPool { - TaskPool::new_internal( - self.num_threads, - self.stack_size, - self.thread_name.as_deref(), - ) + TaskPool::new_internal(self) } } @@ -88,36 +115,42 @@ impl TaskPool { TaskPoolBuilder::new().build() } - fn new_internal( - num_threads: Option, - stack_size: Option, - thread_name: Option<&str>, - ) -> Self { + fn new_internal(builder: TaskPoolBuilder) -> Self { let (shutdown_tx, shutdown_rx) = async_channel::unbounded::<()>(); let executor = Arc::new(async_executor::Executor::new()); - let num_threads = num_threads.unwrap_or_else(crate::available_parallelism); + let num_threads = builder + .num_threads + .unwrap_or_else(crate::available_parallelism); let threads = (0..num_threads) .map(|i| { let ex = Arc::clone(&executor); let shutdown_rx = shutdown_rx.clone(); - let thread_name = if let Some(thread_name) = thread_name { + let thread_name = if let Some(thread_name) = builder.thread_name.as_deref() { format!("{thread_name} ({i})") } else { format!("TaskPool ({i})") }; let mut thread_builder = thread::Builder::new().name(thread_name); - if let Some(stack_size) = stack_size { + if let Some(stack_size) = builder.stack_size { thread_builder = thread_builder.stack_size(stack_size); } + let on_thread_spawn = builder.on_thread_spawn.clone(); + let on_thread_destroy = builder.on_thread_destroy.clone(); + thread_builder .spawn(move || { TaskPool::LOCAL_EXECUTOR.with(|local_executor| { + if let Some(on_thread_spawn) = on_thread_spawn { + on_thread_spawn(); + drop(on_thread_spawn); + } + let _destructor = CallOnDrop(on_thread_destroy); loop { let res = std::panic::catch_unwind(|| { let tick_forever = async move { @@ -452,6 +485,57 @@ mod tests { assert_eq!(count.load(Ordering::Relaxed), 100); } + #[test] + fn test_thread_callbacks() { + let counter = Arc::new(AtomicI32::new(0)); + let start_counter = counter.clone(); + { + let barrier = Arc::new(Barrier::new(11)); + let last_barrier = barrier.clone(); + // Build and immediately drop to terminate + let _pool = TaskPoolBuilder::new() + .num_threads(10) + .on_thread_spawn(move || { + start_counter.fetch_add(1, Ordering::Relaxed); + barrier.clone().wait(); + }) + .build(); + last_barrier.wait(); + assert_eq!(10, counter.load(Ordering::Relaxed)); + } + assert_eq!(10, counter.load(Ordering::Relaxed)); + let end_counter = counter.clone(); + { + let _pool = TaskPoolBuilder::new() + .num_threads(20) + .on_thread_destroy(move || { + end_counter.fetch_sub(1, Ordering::Relaxed); + }) + .build(); + assert_eq!(10, counter.load(Ordering::Relaxed)); + } + assert_eq!(-10, counter.load(Ordering::Relaxed)); + let start_counter = counter.clone(); + let end_counter = counter.clone(); + { + let barrier = Arc::new(Barrier::new(6)); + let last_barrier = barrier.clone(); + let _pool = TaskPoolBuilder::new() + .num_threads(5) + .on_thread_spawn(move || { + start_counter.fetch_add(1, Ordering::Relaxed); + barrier.wait(); + }) + .on_thread_destroy(move || { + end_counter.fetch_sub(1, Ordering::Relaxed); + }) + .build(); + last_barrier.wait(); + assert_eq!(-5, counter.load(Ordering::Relaxed)); + } + assert_eq!(-10, counter.load(Ordering::Relaxed)); + } + #[test] fn test_mixed_spawn_on_scope_and_spawn() { let pool = TaskPool::new(); diff --git a/crates/bevy_transform/src/components/global_transform.rs b/crates/bevy_transform/src/components/global_transform.rs index 8fae0724f679a6..bbf059cf25dfdd 100644 --- a/crates/bevy_transform/src/components/global_transform.rs +++ b/crates/bevy_transform/src/components/global_transform.rs @@ -106,6 +106,50 @@ impl GlobalTransform { } } + /// Returns the [`Transform`] `self` would have if it was a child of an entity + /// with the `parent` [`GlobalTransform`]. + /// + /// This is useful if you want to "reparent" an `Entity`. Say you have an entity + /// `e1` that you want to turn into a child of `e2`, but you want `e1` to keep the + /// same global transform, even after re-partenting. You would use: + /// + /// ```rust + /// # use bevy_transform::prelude::{GlobalTransform, Transform}; + /// # use bevy_ecs::prelude::{Entity, Query, Component, Commands}; + /// # use bevy_hierarchy::{prelude::Parent, BuildChildren}; + /// #[derive(Component)] + /// struct ToReparent { + /// new_parent: Entity, + /// } + /// fn reparent_system( + /// mut commands: Commands, + /// mut targets: Query<(&mut Transform, Entity, &GlobalTransform, &ToReparent)>, + /// transforms: Query<&GlobalTransform>, + /// ) { + /// for (mut transform, entity, initial, to_reparent) in targets.iter_mut() { + /// if let Ok(parent_transform) = transforms.get(to_reparent.new_parent) { + /// *transform = initial.reparented_to(parent_transform); + /// commands.entity(entity) + /// .remove::() + /// .set_parent(to_reparent.new_parent); + /// } + /// } + /// } + /// ``` + /// + /// The transform is expected to be non-degenerate and without shearing, or the output + /// will be invalid. + #[inline] + pub fn reparented_to(&self, parent: &GlobalTransform) -> Transform { + let relative_affine = parent.affine().inverse() * self.affine(); + let (scale, rotation, translation) = relative_affine.to_scale_rotation_translation(); + Transform { + translation, + rotation, + scale, + } + } + /// Extracts `scale`, `rotation` and `translation` from `self`. /// /// The transform is expected to be non-degenerate and without shearing, or the output @@ -209,3 +253,60 @@ impl Mul for GlobalTransform { self.transform_point(value) } } + +#[cfg(test)] +mod test { + use super::*; + + use bevy_math::EulerRot::XYZ; + + fn transform_equal(left: GlobalTransform, right: Transform) -> bool { + left.0.abs_diff_eq(right.compute_affine(), 0.01) + } + + #[test] + fn reparented_to_transform_identity() { + fn reparent_to_same(t1: GlobalTransform, t2: GlobalTransform) -> Transform { + t2.mul_transform(t1.into()).reparented_to(&t2) + } + let t1 = GlobalTransform::from(Transform { + translation: Vec3::new(1034.0, 34.0, -1324.34), + rotation: Quat::from_euler(XYZ, 1.0, 0.9, 2.1), + scale: Vec3::new(1.0, 1.0, 1.0), + }); + let t2 = GlobalTransform::from(Transform { + translation: Vec3::new(0.0, -54.493, 324.34), + rotation: Quat::from_euler(XYZ, 1.9, 0.3, 3.0), + scale: Vec3::new(1.345, 1.345, 1.345), + }); + let retransformed = reparent_to_same(t1, t2); + assert!( + transform_equal(t1, retransformed), + "t1:{:#?} retransformed:{:#?}", + t1.compute_transform(), + retransformed, + ); + } + #[test] + fn reparented_usecase() { + let t1 = GlobalTransform::from(Transform { + translation: Vec3::new(1034.0, 34.0, -1324.34), + rotation: Quat::from_euler(XYZ, 0.8, 1.9, 2.1), + scale: Vec3::new(10.9, 10.9, 10.9), + }); + let t2 = GlobalTransform::from(Transform { + translation: Vec3::new(28.0, -54.493, 324.34), + rotation: Quat::from_euler(XYZ, 0.0, 3.1, 0.1), + scale: Vec3::new(0.9, 0.9, 0.9), + }); + // goal: find `X` such as `t2 * X = t1` + let reparented = t1.reparented_to(&t2); + let t1_prime = t2 * reparented; + assert!( + transform_equal(t1, t1_prime.into()), + "t1:{:#?} t1_prime:{:#?}", + t1.compute_transform(), + t1_prime.compute_transform(), + ); + } +} diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index 2fb90060760126..53de43a51357f5 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -46,21 +46,40 @@ pub fn propagate_transforms( changed |= children_changed; for child in children.iter() { - propagate_recursive( - &global_transform, - &transform_query, - &parent_query, - &children_query, - entity, - *child, - changed, - ); + // SAFETY: + // - We may operate as if the hierarchy is consistent, since `propagate_recursive` will panic before continuing + // to propagate if it encounters an entity with inconsistent parentage. + // - Since each root entity is unique and the hierarchy is consistent and forest-like, + // other root entities' `propagate_recursive` calls will not conflict with this one. + // - Since this is the only place where `transform_query` gets used, there will be no conflicting fetches elsewhere. + unsafe { + propagate_recursive( + &global_transform, + &transform_query, + &parent_query, + &children_query, + entity, + *child, + changed, + ); + } } }, ); } -fn propagate_recursive( +/// Recursively propagates the transforms for `entity` and all of its descendants. +/// +/// # Panics +/// +/// If `entity` or any of its descendants have a malformed hierarchy. +/// The panic will occur before propagating the transforms of any malformed entities and their descendants. +/// +/// # Safety +/// +/// While this function is running, `unsafe_transform_query` must not have any fetches for `entity`, +/// nor any of its descendants. +unsafe fn propagate_recursive( parent: &GlobalTransform, unsafe_transform_query: &Query< (&Transform, Changed, &mut GlobalTransform), @@ -71,7 +90,6 @@ fn propagate_recursive( expected_parent: Entity, entity: Entity, mut changed: bool, - // We use a result here to use the `?` operator. Ideally we'd use a try block instead ) { let Ok(actual_parent) = parent_query.get(entity) else { panic!("Propagated child for {:?} has no Parent component!", entity); @@ -126,15 +144,19 @@ fn propagate_recursive( // If our `Children` has changed, we need to recalculate everything below us changed |= changed_children; for child in children { - propagate_recursive( - &global_matrix, - unsafe_transform_query, - parent_query, - children_query, - entity, - *child, - changed, - ); + // SAFETY: The caller guarantees that `unsafe_transform_query` will not be fetched + // for any descendants of `entity`, so it is safe to call `propagate_recursive` for each child. + unsafe { + propagate_recursive( + &global_matrix, + unsafe_transform_query, + parent_query, + children_query, + entity, + *child, + changed, + ); + } } } diff --git a/crates/bevy_ui/Cargo.toml b/crates/bevy_ui/Cargo.toml index 8b85a40b088a36..f0724e65d5e971 100644 --- a/crates/bevy_ui/Cargo.toml +++ b/crates/bevy_ui/Cargo.toml @@ -30,7 +30,7 @@ bevy_window = { path = "../bevy_window", version = "0.9.0" } bevy_utils = { path = "../bevy_utils", version = "0.9.0" } # other -taffy = "0.1.0" +taffy = "0.2.2" serde = { version = "1", features = ["derive"] } smallvec = { version = "1.6", features = ["union", "const_generics"] } bytemuck = { version = "1.5", features = ["derive"] } diff --git a/crates/bevy_ui/src/camera_config.rs b/crates/bevy_ui/src/camera_config.rs index db01b29fd919d9..e653e15b97dbef 100644 --- a/crates/bevy_ui/src/camera_config.rs +++ b/crates/bevy_ui/src/camera_config.rs @@ -11,7 +11,6 @@ use bevy_render::extract_component::ExtractComponent; /// When a [`Camera`] doesn't have the [`UiCameraConfig`] component, /// it will display the UI by default. /// -/// [`Camera`]: bevy_render::camera::Camera #[derive(Component, Clone)] pub struct UiCameraConfig { /// Whether to output UI to this camera view. diff --git a/crates/bevy_ui/src/flex/convert.rs b/crates/bevy_ui/src/flex/convert.rs index c959aa9dba0b24..c60feb97cd5914 100644 --- a/crates/bevy_ui/src/flex/convert.rs +++ b/crates/bevy_ui/src/flex/convert.rs @@ -8,8 +8,8 @@ pub fn from_rect( rect: UiRect, ) -> taffy::geometry::Rect { taffy::geometry::Rect { - start: from_val(scale_factor, rect.left), - end: from_val(scale_factor, rect.right), + left: from_val(scale_factor, rect.left), + right: from_val(scale_factor, rect.right), top: from_val(scale_factor, rect.top), bottom: from_val(scale_factor, rect.bottom), } @@ -52,10 +52,8 @@ pub fn from_style(scale_factor: f64, value: &Style) -> taffy::style::Style { size: from_val_size(scale_factor, value.size), min_size: from_val_size(scale_factor, value.min_size), max_size: from_val_size(scale_factor, value.max_size), - aspect_ratio: match value.aspect_ratio { - Some(value) => taffy::number::Number::Defined(value), - None => taffy::number::Number::Undefined, - }, + aspect_ratio: value.aspect_ratio, + gap: from_val_size(scale_factor, value.gap), } } diff --git a/crates/bevy_ui/src/flex/mod.rs b/crates/bevy_ui/src/flex/mod.rs index ebe6e1ad4d3948..43715a8cbd4ae4 100644 --- a/crates/bevy_ui/src/flex/mod.rs +++ b/crates/bevy_ui/src/flex/mod.rs @@ -14,7 +14,10 @@ use bevy_transform::components::Transform; use bevy_utils::HashMap; use bevy_window::{Window, WindowId, WindowScaleFactorChanged, Windows}; use std::fmt; -use taffy::{number::Number, Taffy}; +use taffy::{ + prelude::{AvailableSpace, Size}, + Taffy, +}; #[derive(Resource)] pub struct FlexSurface { @@ -63,7 +66,7 @@ impl FlexSurface { let taffy_style = convert::from_style(scale_factor, style); let taffy_node = self.entity_to_taffy.entry(entity).or_insert_with(|| { added = true; - taffy.new_node(taffy_style, &Vec::new()).unwrap() + taffy.new_leaf(taffy_style).unwrap() }); if !added { @@ -81,19 +84,23 @@ impl FlexSurface { let taffy = &mut self.taffy; let taffy_style = convert::from_style(scale_factor, style); let measure = taffy::node::MeasureFunc::Boxed(Box::new( - move |constraints: taffy::geometry::Size| { + move |constraints: Size>, _available: Size| { let mut size = convert::from_f32_size(scale_factor, calculated_size.size); match (constraints.width, constraints.height) { - (Number::Undefined, Number::Undefined) => {} - (Number::Defined(width), Number::Undefined) => { - size.height = width * size.height / size.width; + (None, None) => {} + (Some(width), None) => { + if calculated_size.preserve_aspect_ratio { + size.height = width * size.height / size.width; + } size.width = width; } - (Number::Undefined, Number::Defined(height)) => { - size.width = height * size.width / size.height; + (None, Some(height)) => { + if calculated_size.preserve_aspect_ratio { + size.width = height * size.width / size.height; + } size.height = height; } - (Number::Defined(width), Number::Defined(height)) => { + (Some(width), Some(height)) => { size.width = width; size.height = height; } @@ -106,7 +113,7 @@ impl FlexSurface { self.taffy.set_style(*taffy_node, taffy_style).unwrap(); self.taffy.set_measure(*taffy_node, Some(measure)).unwrap(); } else { - let taffy_node = taffy.new_leaf(taffy_style, measure).unwrap(); + let taffy_node = taffy.new_leaf(taffy_style).unwrap(); self.entity_to_taffy.insert(entity, taffy_node); } } @@ -139,11 +146,10 @@ without UI components as a child of an entity with UI components, results may be pub fn update_window(&mut self, window: &Window) { let taffy = &mut self.taffy; - let node = self.window_nodes.entry(window.id()).or_insert_with(|| { - taffy - .new_node(taffy::style::Style::default(), &Vec::new()) - .unwrap() - }); + let node = self + .window_nodes + .entry(window.id()) + .or_insert_with(|| taffy.new_leaf(taffy::style::Style::default()).unwrap()); taffy .set_style( @@ -174,7 +180,7 @@ without UI components as a child of an entity with UI components, results may be pub fn compute_window_layouts(&mut self) { for window_node in self.window_nodes.values() { self.taffy - .compute_layout(*window_node, taffy::geometry::Size::undefined()) + .compute_layout(*window_node, Size::MAX_CONTENT) .unwrap(); } } @@ -183,7 +189,7 @@ without UI components as a child of an entity with UI components, results may be pub fn remove_entities(&mut self, entities: impl IntoIterator) { for entity in entities { if let Some(node) = self.entity_to_taffy.remove(&entity) { - self.taffy.remove(node); + self.taffy.remove(node).unwrap(); } } } @@ -206,7 +212,7 @@ with UI components as a child of an entity without UI components, results may be #[derive(Debug)] pub enum FlexError { InvalidHierarchy, - TaffyError(taffy::Error), + TaffyError(taffy::error::TaffyError), } #[allow(clippy::too_many_arguments)] @@ -236,12 +242,6 @@ pub fn flex_node_system( let logical_to_physical_factor = windows.scale_factor(WindowId::primary()); let scale_factor = logical_to_physical_factor * ui_scale.scale; - if scale_factor_events.iter().next_back().is_some() || ui_scale.is_changed() { - update_changed(&mut flex_surface, scale_factor, full_node_query); - } else { - update_changed(&mut flex_surface, scale_factor, node_query); - } - fn update_changed( flex_surface: &mut FlexSurface, scaling_factor: f64, @@ -258,6 +258,12 @@ pub fn flex_node_system( } } + if scale_factor_events.iter().next_back().is_some() || ui_scale.is_changed() { + update_changed(&mut flex_surface, scale_factor, full_node_query); + } else { + update_changed(&mut flex_surface, scale_factor, node_query); + } + for (entity, style, calculated_size) in &changed_size_query { flex_surface.upsert_leaf(entity, style, *calculated_size, scale_factor); } diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index b086eb47a3ad21..8fd7d49f99cb2e 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -71,7 +71,7 @@ pub struct ImageBundle { pub calculated_size: CalculatedSize, /// The background color, which serves as a "fill" for this node /// - /// When combined with `UiImage`, tints the provided image. + /// Combines with `UiImage` to tint the provided image. pub background_color: BackgroundColor, /// The image of the node pub image: UiImage, @@ -178,7 +178,7 @@ impl Default for TextBundle { } /// A UI node that is a button -#[derive(Bundle, Clone, Debug)] +#[derive(Bundle, Clone, Debug, Default)] pub struct ButtonBundle { /// Describes the size of the node pub node: Node, @@ -213,22 +213,3 @@ pub struct ButtonBundle { /// Indicates the depth at which the node should appear in the UI pub z_index: ZIndex, } - -impl Default for ButtonBundle { - fn default() -> Self { - ButtonBundle { - button: Button, - interaction: Default::default(), - focus_policy: Default::default(), - node: Default::default(), - style: Default::default(), - background_color: Default::default(), - image: Default::default(), - transform: Default::default(), - global_transform: Default::default(), - visibility: Default::default(), - computed_visibility: Default::default(), - z_index: Default::default(), - } - } -} diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 527e96538f1f39..ee05cf6f8a21b2 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -85,18 +85,13 @@ impl Node for UiPassNode { depth_stencil_attachment: None, }; - let draw_functions = world.resource::>(); - let render_pass = render_context .command_encoder .begin_render_pass(&pass_descriptor); + let mut render_pass = TrackedRenderPass::new(render_pass); + + transparent_phase.render(&mut render_pass, world, view_entity); - let mut draw_functions = draw_functions.write(); - let mut tracked_pass = TrackedRenderPass::new(render_pass); - for item in &transparent_phase.items { - let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); - draw_function.draw(world, &mut tracked_pass, view_entity, item); - } Ok(()) } } @@ -111,6 +106,11 @@ pub struct TransparentUi { impl PhaseItem for TransparentUi { type SortKey = FloatOrd; + #[inline] + fn entity(&self) -> Entity { + self.entity + } + #[inline] fn sort_key(&self) -> Self::SortKey { self.sort_key @@ -122,13 +122,6 @@ impl PhaseItem for TransparentUi { } } -impl EntityPhaseItem for TransparentUi { - #[inline] - fn entity(&self) -> Entity { - self.entity - } -} - impl CachedRenderPipelinePhaseItem for TransparentUi { #[inline] fn cached_pipeline(&self) -> CachedRenderPipelineId { @@ -144,16 +137,18 @@ pub type DrawUi = ( ); pub struct SetUiViewBindGroup; -impl EntityRenderCommand for SetUiViewBindGroup { - type Param = (SRes, SQuery>); +impl RenderCommand

for SetUiViewBindGroup { + type Param = SRes; + type ViewWorldQuery = Read; + type ItemWorldQuery = (); fn render<'w>( - view: Entity, - _item: Entity, - (ui_meta, view_query): SystemParamItem<'w, '_, Self::Param>, + _item: &P, + view_uniform: &'w ViewUniformOffset, + _entity: (), + ui_meta: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let view_uniform = view_query.get(view).unwrap(); pass.set_bind_group( I, ui_meta.into_inner().view_bind_group.as_ref().unwrap(), @@ -163,34 +158,38 @@ impl EntityRenderCommand for SetUiViewBindGroup { } } pub struct SetUiTextureBindGroup; -impl EntityRenderCommand for SetUiTextureBindGroup { - type Param = (SRes, SQuery>); +impl RenderCommand

for SetUiTextureBindGroup { + type Param = SRes; + type ViewWorldQuery = (); + type ItemWorldQuery = Read; + #[inline] fn render<'w>( - _view: Entity, - item: Entity, - (image_bind_groups, query_batch): SystemParamItem<'w, '_, Self::Param>, + _item: &P, + _view: (), + batch: &'w UiBatch, + image_bind_groups: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let batch = query_batch.get(item).unwrap(); let image_bind_groups = image_bind_groups.into_inner(); - pass.set_bind_group(I, image_bind_groups.values.get(&batch.image).unwrap(), &[]); RenderCommandResult::Success } } pub struct DrawUiNode; -impl EntityRenderCommand for DrawUiNode { - type Param = (SRes, SQuery>); +impl RenderCommand

for DrawUiNode { + type Param = SRes; + type ViewWorldQuery = (); + type ItemWorldQuery = Read; + #[inline] fn render<'w>( - _view: Entity, - item: Entity, - (ui_meta, query_batch): SystemParamItem<'w, '_, Self::Param>, + _item: &P, + _view: (), + batch: &'w UiBatch, + ui_meta: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let batch = query_batch.get(item).unwrap(); - pass.set_vertex_buffer(0, ui_meta.into_inner().vertices.buffer().unwrap().slice(..)); pass.draw(batch.range.clone(), 0..1); RenderCommandResult::Success diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 738d578d6e5a39..6b1dd0343029c2 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -237,6 +237,10 @@ pub struct Style { pub aspect_ratio: Option, /// How to handle overflow pub overflow: Overflow, + /// The size of the gutters between the rows and columns of the flexbox layout + /// + /// Values of `Size::UNDEFINED` and `Size::AUTO` are treated as zero. + pub gap: Size, } impl Default for Style { @@ -263,6 +267,7 @@ impl Default for Style { max_size: Size::AUTO, aspect_ratio: Default::default(), overflow: Default::default(), + gap: Size::UNDEFINED, } } } @@ -434,6 +439,8 @@ pub enum FlexWrap { pub struct CalculatedSize { /// The size of the node pub size: Size, + /// Whether to attempt to preserve the aspect ratio when determing the layout for this item + pub preserve_aspect_ratio: bool, } /// The background color of the node diff --git a/crates/bevy_ui/src/widget/image.rs b/crates/bevy_ui/src/widget/image.rs index 2916a6295223a3..fa2e011232cec3 100644 --- a/crates/bevy_ui/src/widget/image.rs +++ b/crates/bevy_ui/src/widget/image.rs @@ -21,6 +21,7 @@ pub fn update_image_calculated_size_system( // Update only if size has changed to avoid needless layout calculations if size != calculated_size.size { calculated_size.size = size; + calculated_size.preserve_aspect_ratio = true; } } } diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 3364c850b3c287..5967d08d9caa4a 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -63,18 +63,20 @@ impl WinitWindows { window_descriptor.height as u32, )), )), - _ => { + WindowMode::Windowed => { if let Some(sf) = scale_factor_override { winit_window_builder.with_inner_size(logical_size.to_physical::(sf)) } else { winit_window_builder.with_inner_size(logical_size) } } + }; + + winit_window_builder = winit_window_builder .with_resizable(window_descriptor.resizable) .with_decorations(window_descriptor.decorations) .with_transparent(window_descriptor.transparent) - .with_always_on_top(window_descriptor.always_on_top), - }; + .with_always_on_top(window_descriptor.always_on_top); let constraints = window_descriptor.resize_constraints.check_constraints(); let min_inner_size = LogicalSize { diff --git a/docs/linux_dependencies.md b/docs/linux_dependencies.md index 652c5cf13571eb..ec7b16b78c9567 100644 --- a/docs/linux_dependencies.md +++ b/docs/linux_dependencies.md @@ -101,29 +101,44 @@ Depending on your graphics card, you may have to install one of the following: sudo xbps-install -S pkgconf alsa-lib-devel libX11-devel eudev-libudev-devel ``` -## NixOS +## [Nix](https://nixos.org) Add a `shell.nix` file to the root of the project containing: ```nix -{ pkgs ? import {} }: -with pkgs; mkShell rec { +{ pkgs ? import { } }: + +with pkgs; + +mkShell rec { nativeBuildInputs = [ pkg-config - llvmPackages.bintools # To use lld linker ]; buildInputs = [ - udev alsaLib vulkan-loader - xlibsWrapper xorg.libXcursor xorg.libXrandr xorg.libXi # To use x11 feature - libxkbcommon wayland # To use wayland feature + udev alsa-lib vulkan-loader + xorg.libX11 xorg.libXcursor xorg.libXi xorg.libXrandr # To use the x11 feature + libxkbcommon wayland # To use the wayland feature ]; - LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs; + LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs; } ``` -And enter it by just running `nix-shell`. You should be able compile Bevy programs using `cargo run` within this nix-shell. You can do this in one line with `nix-shell --run "cargo run"`. +And enter it by just running `nix-shell`. +You should be able compile Bevy programs using `cargo run` within this nix-shell. +You can do this in one line with `nix-shell --run "cargo run"`. + +This is also possible with [Nix flakes](https://nixos.org/manual/nix/unstable/command-ref/new-cli/nix3-flake.html). +Instead of creating `shell.nix`, you just need to add the derivation (`mkShell`) +to your `devShells` in `flake.nix`. Run `nix develop` to enter the shell and +`nix develop -c cargo run` to run the program. See +[Nix's documentation](https://nixos.org/manual/nix/unstable/command-ref/new-cli/nix3-develop.html) +for more information about `devShells`. + +Note that this template does not add Rust to the environment because there are many ways to do it. +For example, to use stable Rust from nixpkgs, you can add `cargo` and `rustc` to `nativeBuildInputs`. -Note that this template does not add Rust to the environment because there are many ways to do it. For example, to use stable Rust from nixpkgs you can add `cargo` to `nativeBuildInputs`. +[Here](https://github.com/NixOS/nixpkgs/blob/master/pkgs/games/jumpy/default.nix) +is an example of packaging a Bevy program in nix. ## [OpenSUSE](https://www.opensuse.org/) diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 977095e87f9f03..4147a4a4966f41 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -103,7 +103,7 @@ fn star( // The `Handle` needs to be wrapped in a `Mesh2dHandle` to use 2d rendering instead of 3d Mesh2dHandle(meshes.add(star)), // This bundle's components are needed for something to be rendered - SpatialBundle::VISIBLE_IDENTITY, + SpatialBundle::INHERITED_IDENTITY, )); // Spawn the camera diff --git a/examples/3d/render_to_texture.rs b/examples/3d/render_to_texture.rs index 80a95bc1265de3..043d43065756fb 100644 --- a/examples/3d/render_to_texture.rs +++ b/examples/3d/render_to_texture.rs @@ -102,7 +102,7 @@ fn setup( }, camera: Camera { // render before the "main pass" camera - priority: -1, + order: -1, target: RenderTarget::Image(image_handle.clone()), ..default() }, diff --git a/examples/3d/skybox.rs b/examples/3d/skybox.rs index 460c79d1d11911..c9dadafd5b8f13 100644 --- a/examples/3d/skybox.rs +++ b/examples/3d/skybox.rs @@ -227,7 +227,7 @@ impl AsBindGroup for CubemapMaterial { render_device: &RenderDevice, images: &RenderAssets, _fallback_image: &FallbackImage, - ) -> Result, AsBindGroupError> { + ) -> Result, AsBindGroupError> { let base_color_texture = self .base_color_texture .as_ref() diff --git a/examples/3d/split_screen.rs b/examples/3d/split_screen.rs index 06756b22a14a1e..d2741efd657841 100644 --- a/examples/3d/split_screen.rs +++ b/examples/3d/split_screen.rs @@ -61,7 +61,7 @@ fn setup( transform: Transform::from_xyz(100.0, 100., 150.0).looking_at(Vec3::ZERO, Vec3::Y), camera: Camera { // Renders the right camera after the left camera, which has a default priority of 0 - priority: 1, + order: 1, ..default() }, camera_3d: Camera3d { diff --git a/examples/3d/two_passes.rs b/examples/3d/two_passes.rs index 3bd14e3079fb08..bb576d5c8e063a 100644 --- a/examples/3d/two_passes.rs +++ b/examples/3d/two_passes.rs @@ -53,7 +53,7 @@ fn setup( }, camera: Camera { // renders after / on top of the main camera - priority: 1, + order: 1, ..default() }, ..default() diff --git a/examples/3d/wireframe.rs b/examples/3d/wireframe.rs index c20757e5b538fd..ef7599eda57c5a 100644 --- a/examples/3d/wireframe.rs +++ b/examples/3d/wireframe.rs @@ -3,16 +3,17 @@ use bevy::{ pbr::wireframe::{Wireframe, WireframeConfig, WireframePlugin}, prelude::*, - render::{render_resource::WgpuFeatures, settings::WgpuSettings}, + render::{render_resource::WgpuFeatures, settings::WgpuSettings, RenderPlugin}, }; fn main() { App::new() - .insert_resource(WgpuSettings { - features: WgpuFeatures::POLYGON_MODE_LINE, - ..default() - }) - .add_plugins(DefaultPlugins) + .add_plugins(DefaultPlugins.set(RenderPlugin { + wgpu_settings: WgpuSettings { + features: WgpuFeatures::POLYGON_MODE_LINE, + ..default() + }, + })) .add_plugin(WireframePlugin) .add_startup_system(setup) .run(); diff --git a/examples/README.md b/examples/README.md index e354e6f2831206..9b732cc7f9cc8b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -295,7 +295,7 @@ Example | Description Example | Description --- | --- [Gamepad Viewer](../examples/tools/gamepad_viewer.rs) | Shows a visualization of gamepad buttons, sticks, and triggers -[Scene Viewer](../examples/tools/scene_viewer.rs) | A simple way to view glTF models with Bevy. Just run `cargo run --release --example scene_viewer /path/to/model.gltf#Scene0`, replacing the path as appropriate. With no arguments it will load the FieldHelmet glTF model from the repository assets subdirectory +[Scene Viewer](../examples/tools/scene_viewer/main.rs) | A simple way to view glTF models with Bevy. Just run `cargo run --release --example scene_viewer /path/to/model.gltf#Scene0`, replacing the path as appropriate. With no arguments it will load the FieldHelmet glTF model from the repository assets subdirectory ## Transforms diff --git a/examples/android/src/lib.rs b/examples/android/src/lib.rs index 6db1cae7033972..244027265a2cf3 100644 --- a/examples/android/src/lib.rs +++ b/examples/android/src/lib.rs @@ -1,19 +1,23 @@ use bevy::{ prelude::*, - render::settings::{WgpuSettings, WgpuSettingsPriority}, + render::{ + settings::{WgpuSettings, WgpuSettingsPriority}, + RenderPlugin, + }, }; // the `bevy_main` proc_macro generates the required android boilerplate #[bevy_main] fn main() { App::new() - // This configures the app to use the most compatible rendering settings. - // They help with compatibility with as many devices as possible. - .insert_resource(WgpuSettings { - priority: WgpuSettingsPriority::Compatibility, - ..default() - }) - .add_plugins(DefaultPlugins) + .add_plugins(DefaultPlugins.set(RenderPlugin { + // This configures the app to use the most compatible rendering settings. + // They help with compatibility with as many devices as possible. + wgpu_settings: WgpuSettings { + priority: WgpuSettingsPriority::Compatibility, + ..default() + }, + })) .add_startup_system(setup) .run(); } diff --git a/examples/animation/animated_transform.rs b/examples/animation/animated_transform.rs index faa090be04c642..ce8d9c7bc468db 100644 --- a/examples/animation/animated_transform.rs +++ b/examples/animation/animated_transform.rs @@ -129,7 +129,7 @@ fn setup( .with_children(|p| { // This entity is just used for animation, but doesn't display anything p.spawn(( - SpatialBundle::VISIBLE_IDENTITY, + SpatialBundle::INHERITED_IDENTITY, // Add the Name component orbit_controller, )) diff --git a/examples/app/no_renderer.rs b/examples/app/no_renderer.rs index ef6ef38c976db3..2c892e4b123fd4 100644 --- a/examples/app/no_renderer.rs +++ b/examples/app/no_renderer.rs @@ -4,14 +4,18 @@ //! //! See also the `headless` example which does not display a window. -use bevy::{prelude::*, render::settings::WgpuSettings}; +use bevy::{ + prelude::*, + render::{settings::WgpuSettings, RenderPlugin}, +}; fn main() { App::new() - .insert_resource(WgpuSettings { - backends: None, - ..default() - }) - .add_plugins(DefaultPlugins) + .add_plugins(DefaultPlugins.set(RenderPlugin { + wgpu_settings: WgpuSettings { + backends: None, + ..default() + }, + })) .run(); } diff --git a/examples/diagnostics/log_diagnostics.rs b/examples/diagnostics/log_diagnostics.rs index 3af2af71df8703..436bba684f78ef 100644 --- a/examples/diagnostics/log_diagnostics.rs +++ b/examples/diagnostics/log_diagnostics.rs @@ -17,5 +17,7 @@ fn main() { // .add_plugin(bevy::diagnostic::EntityCountDiagnosticsPlugin::default()) // Uncomment this to add an asset count diagnostics: // .add_plugin(bevy::asset::diagnostic::AssetCountDiagnosticsPlugin::::default()) + // Uncomment this to add system info diagnostics: + // .add_plugin(bevy::diagnostic::SystemInformationDiagnosticsPlugin::default()) .run(); } diff --git a/examples/ios/Cargo.toml b/examples/ios/Cargo.toml index f3e2df4f6a456c..2e2d425dd287cf 100644 --- a/examples/ios/Cargo.toml +++ b/examples/ios/Cargo.toml @@ -11,14 +11,4 @@ name = "bevy_ios_example" crate-type = ["staticlib"] [dependencies] -bevy = { path = "../../", features = [ - "bevy_audio", - "bevy_winit", - "bevy_core_pipeline", - "bevy_pbr", - "bevy_render", - "bevy_text", - "bevy_ui", - "vorbis", - "filesystem_watcher" -], default-features = false} +bevy = { path = "../../" } diff --git a/examples/shader/post_processing.rs b/examples/shader/post_processing.rs index 83c5f9085ec83f..0f0aa223cbd41d 100644 --- a/examples/shader/post_processing.rs +++ b/examples/shader/post_processing.rs @@ -146,7 +146,7 @@ fn setup( Camera2dBundle { camera: Camera { // renders after the first main camera which has default value: 0. - priority: 1, + order: 1, ..default() }, ..Camera2dBundle::default() diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index 5d8c8b816f4733..7217722253b50c 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -13,8 +13,8 @@ use bevy::{ mesh::{GpuBufferInfo, MeshVertexBufferLayout}, render_asset::RenderAssets, render_phase::{ - AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase, - SetItemPipeline, TrackedRenderPass, + AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, + RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::*, renderer::RenderDevice, @@ -35,7 +35,7 @@ fn main() { fn setup(mut commands: Commands, mut meshes: ResMut>) { commands.spawn(( meshes.add(Mesh::from(shape::Cube { size: 0.5 })), - SpatialBundle::VISIBLE_IDENTITY, + SpatialBundle::INHERITED_IDENTITY, InstanceMaterialData( (1..=10) .flat_map(|x| (1..=10).map(move |y| (x as f32 / 10.0, y as f32 / 10.0))) @@ -223,22 +223,19 @@ type DrawCustom = ( pub struct DrawMeshInstanced; -impl EntityRenderCommand for DrawMeshInstanced { - type Param = ( - SRes>, - SQuery>>, - SQuery>, - ); +impl RenderCommand

for DrawMeshInstanced { + type Param = SRes>; + type ViewWorldQuery = (); + type ItemWorldQuery = (Read>, Read); + #[inline] fn render<'w>( - _view: Entity, - item: Entity, - (meshes, mesh_query, instance_buffer_query): SystemParamItem<'w, '_, Self::Param>, + _item: &P, + _view: (), + (mesh_handle, instance_buffer): (&'w Handle, &'w InstanceBuffer), + meshes: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let mesh_handle = mesh_query.get(item).unwrap(); - let instance_buffer = instance_buffer_query.get_inner(item).unwrap(); - let gpu_mesh = match meshes.into_inner().get(mesh_handle) { Some(gpu_mesh) => gpu_mesh, None => return RenderCommandResult::Failure, diff --git a/examples/stress_tests/README.md b/examples/stress_tests/README.md index 7bfef204a4c96a..b64c46f1a044b7 100644 --- a/examples/stress_tests/README.md +++ b/examples/stress_tests/README.md @@ -1,3 +1,12 @@ # Stress tests -These examples are used to stress test Bevy's performance in various ways. These should be run with the --release argument to cargo or equivalent optimization, otherwise they will be very slow. +These examples are used to stress test Bevy's performance in various ways. These +should be run with the "stress-test" profile to accurately represent performance +in production, otherwise they will run in cargo's default "dev" profile which is +very slow. + +## Example Command + +```bash +cargo run --profile stress-test --example +``` diff --git a/examples/stress_tests/many_foxes.rs b/examples/stress_tests/many_foxes.rs index 4011ddec374f5f..9d5ae3d398b8c8 100644 --- a/examples/stress_tests/many_foxes.rs +++ b/examples/stress_tests/many_foxes.rs @@ -113,7 +113,7 @@ fn setup( let (base_rotation, ring_direction) = ring_directions[ring_index % 2]; let ring_parent = commands .spawn(( - SpatialBundle::VISIBLE_IDENTITY, + SpatialBundle::INHERITED_IDENTITY, ring_direction, Ring { radius }, )) diff --git a/examples/tools/scene_viewer.rs b/examples/tools/scene_viewer.rs deleted file mode 100644 index 5537437af1fc86..00000000000000 --- a/examples/tools/scene_viewer.rs +++ /dev/null @@ -1,578 +0,0 @@ -//! A simple glTF scene viewer made with Bevy. -//! -//! Just run `cargo run --release --example scene_viewer /path/to/model.gltf`, -//! replacing the path as appropriate. -//! In case of multiple scenes, you can select which to display by adapting the file path: `/path/to/model.gltf#Scene1`. -//! With no arguments it will load the `FlightHelmet` glTF model from the repository assets subdirectory. - -use bevy::{ - asset::LoadState, - gltf::Gltf, - input::mouse::MouseMotion, - math::Vec3A, - prelude::*, - render::primitives::{Aabb, Sphere}, - scene::InstanceId, -}; - -use std::f32::consts::*; - -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] -struct CameraControllerCheckSystem; - -fn main() { - println!( - " -Controls: - MOUSE - Move camera orientation - LClick/M - Enable mouse movement - WSAD - forward/back/strafe left/right - LShift - 'run' - E - up - Q - down - L - animate light direction - U - toggle shadows - C - cycle through the camera controller and any cameras loaded from the scene - 5/6 - decrease/increase shadow projection width - 7/8 - decrease/increase shadow projection height - 9/0 - decrease/increase shadow projection near/far - - Space - Play/Pause animation - Enter - Cycle through animations -" - ); - let mut app = App::new(); - app.insert_resource(AmbientLight { - color: Color::WHITE, - brightness: 1.0 / 5.0f32, - }) - .init_resource::() - .add_plugins( - DefaultPlugins - .set(WindowPlugin { - window: WindowDescriptor { - title: "bevy scene viewer".to_string(), - ..default() - }, - ..default() - }) - .set(AssetPlugin { - asset_folder: std::env::var("CARGO_MANIFEST_DIR") - .unwrap_or_else(|_| ".".to_string()), - watch_for_changes: true, - }), - ) - .add_startup_system(setup) - .add_system_to_stage(CoreStage::PreUpdate, scene_load_check) - .add_system_to_stage(CoreStage::PreUpdate, setup_scene_after_load) - .add_system(update_lights) - .add_system(camera_controller) - .add_system(camera_tracker); - - #[cfg(feature = "animation")] - app.add_system(start_animation) - .add_system(keyboard_animation_control); - - app.run(); -} - -#[derive(Resource)] -struct SceneHandle { - gltf_handle: Handle, - scene_index: usize, - #[cfg(feature = "animation")] - animations: Vec>, - instance_id: Option, - is_loaded: bool, - has_light: bool, -} - -fn parse_scene(scene_path: String) -> (String, usize) { - if scene_path.contains('#') { - let gltf_and_scene = scene_path.split('#').collect::>(); - if let Some((last, path)) = gltf_and_scene.split_last() { - if let Some(index) = last - .strip_prefix("Scene") - .and_then(|index| index.parse::().ok()) - { - return (path.join("#"), index); - } - } - } - (scene_path, 0) -} - -fn setup(mut commands: Commands, asset_server: Res) { - let scene_path = std::env::args() - .nth(1) - .unwrap_or_else(|| "assets/models/FlightHelmet/FlightHelmet.gltf".to_string()); - info!("Loading {}", scene_path); - let (file_path, scene_index) = parse_scene(scene_path); - commands.insert_resource(SceneHandle { - gltf_handle: asset_server.load(&file_path), - scene_index, - #[cfg(feature = "animation")] - animations: Vec::new(), - instance_id: None, - is_loaded: false, - has_light: false, - }); -} - -fn scene_load_check( - asset_server: Res, - mut scenes: ResMut>, - gltf_assets: ResMut>, - mut scene_handle: ResMut, - mut scene_spawner: ResMut, -) { - match scene_handle.instance_id { - None => { - if asset_server.get_load_state(&scene_handle.gltf_handle) == LoadState::Loaded { - let gltf = gltf_assets.get(&scene_handle.gltf_handle).unwrap(); - if gltf.scenes.len() > 1 { - info!( - "Displaying scene {} out of {}", - scene_handle.scene_index, - gltf.scenes.len() - ); - info!("You can select the scene by adding '#Scene' followed by a number to the end of the file path (e.g '#Scene1' to load the second scene)."); - } - - let gltf_scene_handle = - gltf.scenes - .get(scene_handle.scene_index) - .unwrap_or_else(|| { - panic!( - "glTF file doesn't contain scene {}!", - scene_handle.scene_index - ) - }); - let scene = scenes.get_mut(gltf_scene_handle).unwrap(); - - let mut query = scene - .world - .query::<(Option<&DirectionalLight>, Option<&PointLight>)>(); - scene_handle.has_light = - query - .iter(&scene.world) - .any(|(maybe_directional_light, maybe_point_light)| { - maybe_directional_light.is_some() || maybe_point_light.is_some() - }); - - scene_handle.instance_id = - Some(scene_spawner.spawn(gltf_scene_handle.clone_weak())); - - #[cfg(feature = "animation")] - { - scene_handle.animations = gltf.animations.clone(); - if !scene_handle.animations.is_empty() { - info!( - "Found {} animation{}", - scene_handle.animations.len(), - if scene_handle.animations.len() == 1 { - "" - } else { - "s" - } - ); - } - } - - info!("Spawning scene..."); - } - } - Some(instance_id) if !scene_handle.is_loaded => { - if scene_spawner.instance_is_ready(instance_id) { - info!("...done!"); - scene_handle.is_loaded = true; - } - } - Some(_) => {} - } -} - -#[cfg(feature = "animation")] -fn start_animation( - mut player: Query<&mut AnimationPlayer>, - mut done: Local, - scene_handle: Res, -) { - if !*done { - if let Ok(mut player) = player.get_single_mut() { - if let Some(animation) = scene_handle.animations.first() { - player.play(animation.clone_weak()).repeat(); - *done = true; - } - } - } -} - -#[cfg(feature = "animation")] -fn keyboard_animation_control( - keyboard_input: Res>, - mut animation_player: Query<&mut AnimationPlayer>, - scene_handle: Res, - mut current_animation: Local, - mut changing: Local, -) { - if scene_handle.animations.is_empty() { - return; - } - - if let Ok(mut player) = animation_player.get_single_mut() { - if keyboard_input.just_pressed(KeyCode::Space) { - if player.is_paused() { - player.resume(); - } else { - player.pause(); - } - } - - if *changing { - // change the animation the frame after return was pressed - *current_animation = (*current_animation + 1) % scene_handle.animations.len(); - player - .play(scene_handle.animations[*current_animation].clone_weak()) - .repeat(); - *changing = false; - } - - if keyboard_input.just_pressed(KeyCode::Return) { - // delay the animation change for one frame - *changing = true; - // set the current animation to its start and pause it to reset to its starting state - player.set_elapsed(0.0).pause(); - } - } -} - -fn setup_scene_after_load( - mut commands: Commands, - mut setup: Local, - mut scene_handle: ResMut, - meshes: Query<(&GlobalTransform, Option<&Aabb>), With>>, -) { - if scene_handle.is_loaded && !*setup { - *setup = true; - // Find an approximate bounding box of the scene from its meshes - if meshes.iter().any(|(_, maybe_aabb)| maybe_aabb.is_none()) { - return; - } - - let mut min = Vec3A::splat(f32::MAX); - let mut max = Vec3A::splat(f32::MIN); - for (transform, maybe_aabb) in &meshes { - let aabb = maybe_aabb.unwrap(); - // If the Aabb had not been rotated, applying the non-uniform scale would produce the - // correct bounds. However, it could very well be rotated and so we first convert to - // a Sphere, and then back to an Aabb to find the conservative min and max points. - let sphere = Sphere { - center: Vec3A::from(transform.transform_point(Vec3::from(aabb.center))), - radius: transform.radius_vec3a(aabb.half_extents), - }; - let aabb = Aabb::from(sphere); - min = min.min(aabb.min()); - max = max.max(aabb.max()); - } - - let size = (max - min).length(); - let aabb = Aabb::from_min_max(Vec3::from(min), Vec3::from(max)); - - info!("Spawning a controllable 3D perspective camera"); - let mut projection = PerspectiveProjection::default(); - projection.far = projection.far.max(size * 10.0); - commands.spawn(( - Camera3dBundle { - projection: projection.into(), - transform: Transform::from_translation( - Vec3::from(aabb.center) + size * Vec3::new(0.5, 0.25, 0.5), - ) - .looking_at(Vec3::from(aabb.center), Vec3::Y), - camera: Camera { - is_active: false, - ..default() - }, - ..default() - }, - CameraController::default(), - )); - - // Spawn a default light if the scene does not have one - if !scene_handle.has_light { - let sphere = Sphere { - center: aabb.center, - radius: aabb.half_extents.length(), - }; - let aabb = Aabb::from(sphere); - let min = aabb.min(); - let max = aabb.max(); - - info!("Spawning a directional light"); - commands.spawn(DirectionalLightBundle { - directional_light: DirectionalLight { - shadow_projection: OrthographicProjection { - left: min.x, - right: max.x, - bottom: min.y, - top: max.y, - near: min.z, - far: max.z, - ..default() - }, - shadows_enabled: false, - ..default() - }, - ..default() - }); - - scene_handle.has_light = true; - } - } -} - -const SCALE_STEP: f32 = 0.1; - -fn update_lights( - key_input: Res>, - time: Res