diff --git a/.github/start-wasm-example/package-lock.json b/.github/start-wasm-example/package-lock.json index 4a39b73a25660..2d4105d6751ca 100644 --- a/.github/start-wasm-example/package-lock.json +++ b/.github/start-wasm-example/package-lock.json @@ -12,16 +12,17 @@ "dotenv": "^16.0.1" }, "devDependencies": { - "@playwright/test": "^1.22.1" + "@playwright/test": "^1.28.1" } }, "node_modules/@playwright/test": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.22.1.tgz", - "integrity": "sha512-8ouMBUboYslHom41W8bnSEn0TwlAMHhCACwOZeuiAgzukj7KobpZ+UBwrGE0jJ0UblJbKAQNRHXL+z7sDSkb6g==", + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.1.tgz", + "integrity": "sha512-xN6spdqrNlwSn9KabIhqfZR7IWjPpFK1835tFNgjrlysaSezuX8PYUwaz38V/yI8TJLG9PkAMEXoHRXYXlpTPQ==", "dev": true, "dependencies": { - "playwright-core": "1.22.1" + "@types/node": "*", + "playwright-core": "1.28.1" }, "bin": { "playwright": "cli.js" @@ -30,6 +31,12 @@ "node": ">=14" } }, + "node_modules/@types/node": { + "version": "18.11.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.11.tgz", + "integrity": "sha512-KJ021B1nlQUBLopzZmPBVuGU9un7WJd/W4ya7Ih02B4Uwky5Nja0yGYav2EfYIk0RR2Q9oVhf60S2XR1BCWJ2g==", + "dev": true + }, "node_modules/dotenv": { "version": "16.0.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", @@ -39,9 +46,9 @@ } }, "node_modules/playwright-core": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.22.1.tgz", - "integrity": "sha512-H+ZUVYnceWNXrRf3oxTEKAr81QzFsCKu5Fp//fEjQvqgKkfA1iX3E9DBrPJpPNOrgVzcE+IqeI0fDmYJe6Ynnw==", + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.1.tgz", + "integrity": "sha512-3PixLnGPno0E8rSBJjtwqTwJe3Yw72QwBBBxNoukIj3lEeBNXwbNiKrNuB1oyQgTBw5QHUhNO3SteEtHaMK6ag==", "dev": true, "bin": { "playwright": "cli.js" @@ -53,23 +60,30 @@ }, "dependencies": { "@playwright/test": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.22.1.tgz", - "integrity": "sha512-8ouMBUboYslHom41W8bnSEn0TwlAMHhCACwOZeuiAgzukj7KobpZ+UBwrGE0jJ0UblJbKAQNRHXL+z7sDSkb6g==", + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.1.tgz", + "integrity": "sha512-xN6spdqrNlwSn9KabIhqfZR7IWjPpFK1835tFNgjrlysaSezuX8PYUwaz38V/yI8TJLG9PkAMEXoHRXYXlpTPQ==", "dev": true, "requires": { - "playwright-core": "1.22.1" + "@types/node": "*", + "playwright-core": "1.28.1" } }, + "@types/node": { + "version": "18.11.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.11.tgz", + "integrity": "sha512-KJ021B1nlQUBLopzZmPBVuGU9un7WJd/W4ya7Ih02B4Uwky5Nja0yGYav2EfYIk0RR2Q9oVhf60S2XR1BCWJ2g==", + "dev": true + }, "dotenv": { "version": "16.0.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==" }, "playwright-core": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.22.1.tgz", - "integrity": "sha512-H+ZUVYnceWNXrRf3oxTEKAr81QzFsCKu5Fp//fEjQvqgKkfA1iX3E9DBrPJpPNOrgVzcE+IqeI0fDmYJe6Ynnw==", + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.1.tgz", + "integrity": "sha512-3PixLnGPno0E8rSBJjtwqTwJe3Yw72QwBBBxNoukIj3lEeBNXwbNiKrNuB1oyQgTBw5QHUhNO3SteEtHaMK6ag==", "dev": true } } diff --git a/.github/start-wasm-example/package.json b/.github/start-wasm-example/package.json index 9e10e8134e657..1ce7e5e2dfb85 100644 --- a/.github/start-wasm-example/package.json +++ b/.github/start-wasm-example/package.json @@ -8,7 +8,7 @@ "author": "", "license": "ISC", "devDependencies": { - "@playwright/test": "^1.22.1" + "@playwright/test": "^1.28.1" }, "dependencies": { "dotenv": "^16.0.1" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00a5dd8040c70..c5b282b74a27c 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/.github/workflows/validation-jobs.yml b/.github/workflows/validation-jobs.yml index 023fad1604d93..7a35bf314e9c8 100644 --- a/.github/workflows/validation-jobs.yml +++ b/.github/workflows/validation-jobs.yml @@ -54,7 +54,7 @@ jobs: run: cargo install --force cargo-apk - name: Build APK - run: ANDROID_NDK_ROOT=$ANDROID_NDK_LATEST_HOME cargo apk build --example android_example + run: ANDROID_NDK_ROOT=$ANDROID_NDK_LATEST_HOME cargo apk build --package bevy-android-example run-examples-on-windows-dx12: runs-on: windows-latest diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 586513eb99b89..ad7f58a9b1ef8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -262,10 +262,11 @@ When in doubt about a matter of architectural philosophy, refer back to [*What w Once you're happy with the work and feel you're reasonably qualified to assess quality in this particular area, leave your `Approved` review on the PR. If you're new to GitHub, check out the [Pull Request Review documentation](https://docs.github.com/en/github/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/about-pull-request-reviews). Anyone can leave reviews ... no special permissions are required! -There are a two main places you can check for things to review: +There are three main places you can check for things to review: -1. Pull requests on [bevy](https://github.com/bevyengine/bevy/pulls) and the [bevy-website](https://github.com/bevyengine/bevy-website/pulls) repos. -2. [RFCs](https://github.com/bevyengine/rfcs), which need extensive thoughtful community input on their design. +1. Pull request which are ready and in need of more reviews on [bevy](https://github.com/bevyengine/bevy/pulls?q=is%3Aopen+is%3Apr+-label%3AS-Ready-For-Final-Review+-draft%3A%3Atrue+-label%3AS-Needs-RFC+-reviewed-by%3A%40me+-author%3A%40me) +2. Pull requests on [bevy](https://github.com/bevyengine/bevy/pulls) and the [bevy-website](https://github.com/bevyengine/bevy-website/pulls) repos. +3. [RFCs](https://github.com/bevyengine/rfcs), which need extensive thoughtful community input on their design. Official focus areas and work done by @cart go through this review process as well. Not even our project lead is exempt from reviews and RFCs! @@ -274,6 +275,35 @@ 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). + +These search filters show the requests that can be merged by maintainers, and those which need a final approval from @cart. + +1. Pulls requests which are ready for maintainers to merge without consultation: [requests to pull](https://github.com/bevyengine/bevy/pulls?q=is%3Aopen+is%3Apr+label%3AS-Ready-For-Final-Review+-label%3AS-Controversial+-label%3AS-Blocked+-label%3AS-Adopt-Me+) +2. Pull requests which need final input from @cart: [requests to verify](https://github.com/bevyengine/bevy/pulls?q=is%3Aopen+is%3Apr+label%3AS-Ready-For-Final-Review+label%3AS-Controversial+) + ### Contributing code Bevy is actively open to code contributions from community members. @@ -282,6 +312,7 @@ If you're new to Bevy, here's the workflow we use: 1. Fork the `bevyengine/bevy` repository on GitHub. You'll need to create a GitHub account if you don't have one already. 2. Make your changes in a local clone of your fork, typically in its own new branch. 1. Try to split your work into separate commits, each with a distinct purpose. Be particularly mindful of this when responding to reviews so it's easy to see what's changed. + 2. Tip: [You can set up a global `.gitignore` file](https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files#configuring-ignored-files-for-all-repositories-on-your-computer) to exclude your operating system/text editor's special/temporary files. (e.g. `.DS_Store`, `thumbs.db`, `*~`, `*.swp` or `*.swo`) This allows us to keep the `.gitignore` file in the repo uncluttered. 3. To test CI validations locally, run the `cargo run -p ci` command. This will run most checks that happen in CI, but can take some time. You can also run sub-commands to iterate faster depending on what you're contributing: * `cargo run -p ci -- lints` - to run formatting and clippy * `cargo run -p ci -- test` - to run tests diff --git a/Cargo.toml b/Cargo.toml index 251e47609645d..11e7cc5fb06d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,10 @@ 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", "examples/ios", "tools/ci", "tools/spancmp", @@ -31,7 +32,13 @@ default = [ "bevy_gilrs", "bevy_scene", "bevy_winit", - "render", + "bevy_core_pipeline", + "bevy_pbr", + "bevy_gltf", + "bevy_render", + "bevy_sprite", + "bevy_text", + "bevy_ui", "png", "hdr", "vorbis", @@ -42,17 +49,6 @@ default = [ # Force dynamic linking, which improves iterative compile times dynamic = ["bevy_dylib"] -# Rendering support -render = [ - "bevy_internal/bevy_core_pipeline", - "bevy_internal/bevy_pbr", - "bevy_internal/bevy_gltf", - "bevy_internal/bevy_render", - "bevy_internal/bevy_sprite", - "bevy_internal/bevy_text", - "bevy_internal/bevy_ui", -] - # Optional bevy crates bevy_animation = ["bevy_internal/bevy_animation"] bevy_asset = ["bevy_internal/bevy_asset"] @@ -1362,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" @@ -1442,6 +1438,16 @@ description = "Illustrates creating and updating a button" category = "UI (User Interface)" wasm = true +[[example]] +name = "window_fallthrough" +path = "examples/ui/window_fallthrough.rs" + +[package.metadata.example.window_fallthrough] +name = "Window Fallthrough" +description = "Illustrates how to access `winit::window::Window`'s `hittest` functionality." +category = "UI (User Interface)" +wasm = false + [[example]] name = "font_atlas_debug" path = "examples/ui/font_atlas_debug.rs" @@ -1597,31 +1603,13 @@ description = "Demonstrates resizing and responding to resizing a window" category = "Window" wasm = true -# Android -[[example]] -crate-type = ["cdylib"] -name = "android_example" -path = "examples/android/android.rs" - -[package.metadata.example.android_example] -hidden = true - -[package.metadata.android] -package = "org.bevyengine.example" -apk_name = "bevyexample" -assets = "assets" -resources = "assets/android-res" -build_targets = ["aarch64-linux-android", "armv7-linux-androideabi"] - -[package.metadata.android.sdk] -target_sdk_version = 31 - -[package.metadata.android.application] -icon = "@mipmap/ic_launcher" -label = "Bevy Example" - [profile.wasm-release] 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 c6613825591ec..16caea1be6c47 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 { @@ -107,9 +109,7 @@ impl Default for App { #[cfg(feature = "bevy_reflect")] app.init_resource::(); - app.add_default_stages() - .add_event::() - .add_system_to_stage(CoreStage::Last, World::clear_trackers); + app.add_default_stages().add_event::(); #[cfg(feature = "bevy_ci_testing")] { @@ -138,6 +138,7 @@ impl App { sub_apps: HashMap::default(), plugin_registry: Vec::default(), plugin_name_added: Default::default(), + is_building_plugin: false, } } @@ -150,20 +151,31 @@ impl App { #[cfg(feature = "trace")] let _bevy_frame_update_span = info_span!("frame").entered(); self.schedule.run(&mut self.world); + for sub_app in self.sub_apps.values_mut() { (sub_app.runner)(&mut self.world, &mut sub_app.app); + sub_app.app.world.clear_trackers(); } + + self.world.clear_trackers(); } /// Starts the application by calling the app's [runner function](Self::set_runner). /// /// 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); } @@ -856,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) } @@ -1103,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_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs index f42db09df654c..31ee5f503adc3 100644 --- a/crates/bevy_audio/src/audio_output.rs +++ b/crates/bevy_audio/src/audio_output.rs @@ -1,17 +1,28 @@ use crate::{Audio, AudioSource, Decodable}; use bevy_asset::{Asset, Assets}; -use bevy_ecs::system::{NonSend, Res, ResMut}; +use bevy_ecs::system::{Res, ResMut, Resource}; use bevy_reflect::TypeUuid; use bevy_utils::tracing::warn; use rodio::{OutputStream, OutputStreamHandle, Sink, Source}; use std::marker::PhantomData; /// Used internally to play audio on the current "audio device" +/// +/// ## Note +/// +/// Initializing this resource will leak [`rodio::OutputStream`](rodio::OutputStream) +/// using [`std::mem::forget`]. +/// This is done to avoid storing this in the struct (and making this `!Send`) +/// while preventing it from dropping (to avoid halting of audio). +/// +/// This is fine when initializing this once (as is default when adding this plugin), +/// since the memory cost will be the same. +/// However, repeatedly inserting this resource into the app will **leak more memory**. +#[derive(Resource)] pub struct AudioOutput where Source: Decodable, { - _stream: Option, stream_handle: Option, phantom: PhantomData, } @@ -22,15 +33,15 @@ where { fn default() -> Self { if let Ok((stream, stream_handle)) = OutputStream::try_default() { + // We leak `OutputStream` to prevent the audio from stopping. + std::mem::forget(stream); Self { - _stream: Some(stream), stream_handle: Some(stream_handle), phantom: PhantomData, } } else { warn!("No audio device found."); Self { - _stream: None, stream_handle: None, phantom: PhantomData, } @@ -84,7 +95,7 @@ where /// Plays audio currently queued in the [`Audio`] resource through the [`AudioOutput`] resource pub fn play_queued_audio_system( - audio_output: NonSend>, + audio_output: Res>, audio_sources: Option>>, mut audio: ResMut>, mut sinks: ResMut>, diff --git a/crates/bevy_audio/src/audio_source.rs b/crates/bevy_audio/src/audio_source.rs index 217c9aa868350..470eccd04f59e 100644 --- a/crates/bevy_audio/src/audio_source.rs +++ b/crates/bevy_audio/src/audio_source.rs @@ -8,7 +8,15 @@ use std::{io::Cursor, sync::Arc}; #[derive(Debug, Clone, TypeUuid)] #[uuid = "7a14806a-672b-443b-8d16-4f18afefa463"] pub struct AudioSource { - /// Raw data of the audio source + /// Raw data of the audio source. + /// + /// The data must be one of the file formats supported by Bevy (`wav`, `ogg`, `flac`, or `mp3`). + /// It is decoded using [`rodio::decoder::Decoder`](https://docs.rs/rodio/latest/rodio/decoder/struct.Decoder.html). + /// + /// The decoder has conditionally compiled methods + /// depending on the features enabled. + /// If the format used is not enabled, + /// then this will panic with an `UnrecognizedFormat` error. pub bytes: Arc<[u8]>, } diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index 7c4db0090c77c..9fc4a5484b88c 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -50,7 +50,7 @@ pub struct AudioPlugin; impl Plugin for AudioPlugin { fn build(&self, app: &mut App) { - app.init_non_send_resource::>() + app.init_resource::>() .add_asset::() .add_asset::() .init_resource::>() diff --git a/crates/bevy_core/src/lib.rs b/crates/bevy_core/src/lib.rs index fbc175c1e3f89..ee21feb2ec9ef 100644 --- a/crates/bevy_core/src/lib.rs +++ b/crates/bevy_core/src/lib.rs @@ -6,7 +6,7 @@ mod name; mod serde; mod task_pool_options; -use bevy_ecs::system::Resource; +use bevy_ecs::system::{ResMut, Resource}; pub use bytemuck::{bytes_of, cast_slice, Pod, Zeroable}; pub use name::*; pub use task_pool_options::*; @@ -22,7 +22,9 @@ use bevy_ecs::entity::Entity; use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; use bevy_utils::{Duration, HashSet, Instant}; use std::borrow::Cow; +use std::ffi::OsString; use std::ops::Range; +use std::path::PathBuf; #[cfg(not(target_arch = "wasm32"))] use bevy_ecs::schedule::IntoSystemDescriptor; @@ -53,6 +55,7 @@ impl Plugin for CorePlugin { register_math_types(app); app.init_resource::(); + app.add_system(update_frame_count); } } @@ -61,6 +64,8 @@ fn register_rust_types(app: &mut App) { .register_type_data::, ReflectSerialize>() .register_type_data::, ReflectDeserialize>() .register_type::() + .register_type::() + .register_type::() .register_type::>() .register_type::>() .register_type::>() @@ -108,6 +113,10 @@ fn register_math_types(app: &mut App) { #[derive(Default, Resource, Clone, Copy)] pub struct FrameCount(pub u32); +fn update_frame_count(mut frame_count: ResMut) { + frame_count.0 = frame_count.0.wrapping_add(1); +} + #[cfg(test)] mod tests { use super::*; @@ -145,4 +154,14 @@ mod tests { compute_rx.try_recv().unwrap(); io_rx.try_recv().unwrap(); } + + #[test] + fn frame_counter_update() { + let mut app = App::new(); + app.add_plugin(CorePlugin::default()); + app.update(); + + let frame_count = app.world.resource::(); + assert_eq!(1, frame_count.0); + } } diff --git a/crates/bevy_core_pipeline/src/bloom/bloom.wgsl b/crates/bevy_core_pipeline/src/bloom/bloom.wgsl index 6ca6f74440542..d4a3822bff072 100644 --- a/crates/bevy_core_pipeline/src/bloom/bloom.wgsl +++ b/crates/bevy_core_pipeline/src/bloom/bloom.wgsl @@ -1,6 +1,6 @@ // Bloom works by creating an intermediate texture with a bunch of mip levels, each half the size of the previous. // You then downsample each mip (starting with the original texture) to the lower resolution mip under it, going in order. -// You then upsample each mip (starting from the smallest mip) and additively blend with the higher resolution mip above it (ending on the original texture). +// You then upsample each mip (starting from the smallest mip) and blend with the higher resolution mip above it (ending on the original texture). // // References: // * http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare @@ -10,8 +10,8 @@ #import bevy_core_pipeline::tonemapping struct BloomUniforms { - intensity: f32, threshold_precomputations: vec4, + viewport: vec4, }; @group(0) @binding(0) @@ -20,8 +20,6 @@ var input_texture: texture_2d; var s: sampler; @group(0) @binding(2) var uniforms: BloomUniforms; -@group(0) @binding(3) -var main_pass_texture: texture_2d; // https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/#3.4 fn soft_threshold(color: vec3) -> vec3 { @@ -111,11 +109,14 @@ fn sample_input_3x3_tent(uv: vec2) -> vec3 { } @fragment -fn downsample_first(@location(0) uv: vec2) -> @location(0) vec4 { - var sample = sample_input_13_tap(uv); +fn downsample_first(@location(0) output_uv: vec2) -> @location(0) vec4 { + let sample_uv = uniforms.viewport.xy + output_uv * uniforms.viewport.zw; + var sample = sample_input_13_tap(sample_uv); sample = clamp(sample, vec3(0.0), vec3(3.40282347E+38)); // Prevent NaNs +#ifdef USE_THRESHOLD sample = soft_threshold(sample); +#endif return vec4(sample, 1.0); } @@ -129,13 +130,3 @@ fn downsample(@location(0) uv: vec2) -> @location(0) vec4 { fn upsample(@location(0) uv: vec2) -> @location(0) vec4 { return vec4(sample_input_3x3_tent(uv), 1.0); } - -@fragment -fn upsample_final(@location(0) uv: vec2) -> @location(0) vec4 { - let main_pass_sample = textureSample(main_pass_texture, s, uv); - let bloom_sample = sample_input_3x3_tent(uv); - - let mixed_sample = mix(main_pass_sample.rgb, bloom_sample, uniforms.intensity); - - return vec4(mixed_sample, main_pass_sample.a); -} diff --git a/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs b/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs new file mode 100644 index 0000000000000..2c35575cd22e2 --- /dev/null +++ b/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs @@ -0,0 +1,197 @@ +use bevy_ecs::{ + prelude::{Component, Entity}, + query::With, + system::{Commands, Query, Res, ResMut, Resource}, + world::{FromWorld, World}, +}; +use bevy_math::Vec4; +use bevy_render::{ + render_resource::{ + BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, + BufferBindingType, CachedRenderPipelineId, ColorTargetState, ColorWrites, FragmentState, + MultisampleState, PipelineCache, PrimitiveState, RenderPipelineDescriptor, + SamplerBindingType, Shader, ShaderStages, ShaderType, SpecializedRenderPipeline, + SpecializedRenderPipelines, TextureSampleType, TextureViewDimension, + }, + renderer::RenderDevice, + view::ExtractedView, +}; + +use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; + +use super::{BloomSettings, BLOOM_SHADER_HANDLE, BLOOM_TEXTURE_FORMAT}; + +#[derive(Component)] +pub struct BloomDownsamplingPipelineIds { + pub id_main: CachedRenderPipelineId, + pub id_first: CachedRenderPipelineId, +} + +#[derive(Resource)] +pub struct BloomDownsamplingPipeline { + /// Layout with just a texture and a sampler + pub bind_group_layout: BindGroupLayout, + /// Layout with a texture, a sampler and downsampling settings + pub extended_bind_group_layout: BindGroupLayout, +} + +#[derive(PartialEq, Eq, Hash, Clone)] +pub struct BloomDownsamplingPipelineKeys { + prefilter: bool, + first_downsample: bool, +} + +/// The uniform struct extracted from [`BloomSettings`] attached to a [`Camera`]. +/// Will be available for use in the Bloom shader in the first downsample pass. +#[doc(hidden)] +#[derive(Component, ShaderType, Clone)] +pub struct BloomDownsamplingUniform { + // Precomputed values used when thresholding, see https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/#3.4 + pub threshold_precomputations: Vec4, + pub viewport: Vec4, +} + +impl FromWorld for BloomDownsamplingPipeline { + fn from_world(world: &mut World) -> Self { + let render_device = world.resource::(); + + // Input texture (higher resolution for downsample, lower resolution for upsample) + let texture = BindGroupLayoutEntry { + binding: 0, + ty: BindingType::Texture { + sample_type: TextureSampleType::Float { filterable: true }, + view_dimension: TextureViewDimension::D2, + multisampled: false, + }, + visibility: ShaderStages::FRAGMENT, + count: None, + }; + + // Sampler + let sampler = BindGroupLayoutEntry { + binding: 1, + ty: BindingType::Sampler(SamplerBindingType::Filtering), + visibility: ShaderStages::FRAGMENT, + count: None, + }; + + // Downsampling settings + let settings = BindGroupLayoutEntry { + binding: 2, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: Some(BloomDownsamplingUniform::min_size()), + }, + visibility: ShaderStages::FRAGMENT, + count: None, + }; + + // Bind group layouts + let bind_group_layout = + render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("bloom_downsampling_bind_group_layout"), + entries: &[texture, sampler], + }); + let extended_bind_group_layout = + render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("bloom_downsampling_bind_group_layout_with_settings"), + entries: &[texture, sampler, settings], + }); + + BloomDownsamplingPipeline { + bind_group_layout, + extended_bind_group_layout, + } + } +} + +impl SpecializedRenderPipeline for BloomDownsamplingPipeline { + type Key = BloomDownsamplingPipelineKeys; + + fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { + let layout = if key.first_downsample { + Some(vec![self.extended_bind_group_layout.clone()]) + } else { + Some(vec![self.bind_group_layout.clone()]) + }; + + let entry_point = if key.first_downsample { + "downsample_first".into() + } else { + "downsample".into() + }; + + let mut shader_defs = vec![]; + + if key.first_downsample { + shader_defs.push("FIRST_DOWNSAMPLE".into()); + } + + if key.prefilter { + shader_defs.push("USE_THRESHOLD".into()); + } + + RenderPipelineDescriptor { + label: Some( + format!( + "bloom_downsampling_pipeline; first ? {}", + key.first_downsample + ) + .into(), + ), + layout, + vertex: fullscreen_shader_vertex_state(), + fragment: Some(FragmentState { + shader: BLOOM_SHADER_HANDLE.typed::(), + shader_defs, + entry_point, + targets: vec![Some(ColorTargetState { + format: BLOOM_TEXTURE_FORMAT, + blend: None, + write_mask: ColorWrites::ALL, + })], + }), + primitive: PrimitiveState::default(), + depth_stencil: None, + multisample: MultisampleState::default(), + } + } +} + +pub fn prepare_downsampling_pipeline( + mut commands: Commands, + mut pipeline_cache: ResMut, + mut pipelines: ResMut>, + pipeline: Res, + views: Query<(Entity, &ExtractedView, &BloomSettings), With>, +) { + for (entity, _view, settings) in &views { + let prefilter = settings.prefilter_settings.threshold > 0.0; + + let pipeline_id = pipelines.specialize( + &mut pipeline_cache, + &pipeline, + BloomDownsamplingPipelineKeys { + prefilter, + first_downsample: false, + }, + ); + + let pipeline_first_id = pipelines.specialize( + &mut pipeline_cache, + &pipeline, + BloomDownsamplingPipelineKeys { + prefilter, + first_downsample: true, + }, + ); + + commands + .entity(entity) + .insert(BloomDownsamplingPipelineIds { + id_first: pipeline_first_id, + id_main: pipeline_id, + }); + } +} diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index d97260bb6ece4..d147deaca1587 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -1,21 +1,32 @@ -use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; +pub mod settings; +pub use self::settings::*; + +mod downsampling_pipeline; +mod upsampling_pipeline; +use self::{downsampling_pipeline::*, upsampling_pipeline::*}; + +use crate::{core_2d, core_3d}; use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, HandleUntyped}; use bevy_ecs::{ prelude::{Component, Entity}, - query::{QueryState, With}, + query::{QueryItem, QueryState, With}, system::{Commands, Query, Res, ResMut, Resource}, world::{FromWorld, World}, }; -use bevy_math::{UVec2, Vec4}; -use bevy_reflect::{Reflect, TypeUuid}; +use bevy_math::{UVec2, UVec4, Vec4}; +use bevy_reflect::TypeUuid; use bevy_render::{ camera::ExtractedCamera, - prelude::Camera, + extract_component::{ + ComponentUniforms, DynamicUniformIndex, ExtractComponent, ExtractComponentPlugin, + UniformComponentPlugin, + }, + prelude::{Camera, Color}, render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotInfo, SlotType}, render_phase::TrackedRenderPass, render_resource::*, - renderer::{RenderContext, RenderDevice, RenderQueue}, + renderer::{RenderContext, RenderDevice}, texture::{CachedTexture, TextureCache}, view::ViewTarget, Extract, RenderApp, RenderStage, @@ -25,22 +36,49 @@ use bevy_utils::tracing::info_span; use bevy_utils::HashMap; use std::num::NonZeroU32; -pub mod draw_3d_graph { - pub mod node { - /// Label for the bloom render node. - pub const BLOOM: &str = "bloom_3d"; - } -} -pub mod draw_2d_graph { - pub mod node { - /// Label for the bloom render node. - pub const BLOOM: &str = "bloom_2d"; - } -} - const BLOOM_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 929599476923908); +const BLOOM_TEXTURE_FORMAT: TextureFormat = TextureFormat::Rg11b10Float; + +impl BloomSettings { + /// Calculates blend intesnities of blur pyramid levels + /// during the upsampling+compositing stage. + /// + /// The function assumes all pyramid levels are upsampled and + /// blended into higher frequency ones using this function to + /// calculate blend levels every time. The final (highest frequency) + /// pyramid level in not blended into anything therefore this function + /// is not applied to it. As a result, the *mip* Earameter of 0 indicates + /// the second-highest frequency pyramid level (in our case that is the + /// 0th mip of the bloom texture with the original image being the + /// actual highest frequency level). + /// + /// Parameters: + /// * *mip* - the index of the lower frequency pyramid level (0 - max_mip, where 0 indicates highest frequency mip but not the highest frequency image). + /// * *max_mip* - the index of the lowest frequency pyramid level. + /// + /// This function can be visually previewed for all values of *mip* (normalized) with tweakable + /// BloomSettings parameters on [Desmos graphing calculator](https://www.desmos.com/calculator/ncc8xbhzzl). + fn compute_blend_factor(&self, mip: f32, max_mip: f32) -> f32 { + let x = mip / max_mip; + + let mut lf_boost = + (1.0 - (1.0 - x).powf(1.0 / (1.0 - self.lf_boost_curvature))) * self.lf_boost; + let high_pass_lq = + 1.0 - ((x - self.high_pass_frequency) / self.high_pass_frequency).clamp(0.0, 1.0); + // let high_pass_hq = 0.5 + 0.5 * ((x - self.high_pass_frequency) / self.high_pass_frequency).clamp(0.0, 1.0).mul(std::f32::consts::PI).cos(); + let high_pass = high_pass_lq; + + lf_boost *= match self.composite_mode { + BloomCompositeMode::EnergyConserving => 1.0 - self.intensity, + BloomCompositeMode::Additive => 1.0, + }; + + (self.intensity + lf_boost) * high_pass + } +} + pub struct BloomPlugin; impl Plugin for BloomPlugin { @@ -48,6 +86,10 @@ impl Plugin for BloomPlugin { load_internal_asset!(app, BLOOM_SHADER_HANDLE, "bloom.wgsl", Shader::from_wgsl); app.register_type::(); + app.register_type::(); + app.register_type::(); + app.add_plugin(ExtractComponentPlugin::::default()); + app.add_plugin(UniformComponentPlugin::::default()); let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, @@ -56,10 +98,16 @@ impl Plugin for BloomPlugin { render_app .init_resource::() - .init_resource::() + .init_resource::() + .init_resource::() + .init_resource::>() + .init_resource::>() + // .init_resource::() .add_system_to_stage(RenderStage::Extract, extract_bloom_settings) .add_system_to_stage(RenderStage::Prepare, prepare_bloom_textures) - .add_system_to_stage(RenderStage::Prepare, prepare_bloom_uniforms) + // .add_system_to_stage(RenderStage::Prepare, prepare_bloom_uniforms) + .add_system_to_stage(RenderStage::Prepare, prepare_downsampling_pipeline) + .add_system_to_stage(RenderStage::Prepare, prepare_upsampling_pipeline) .add_system_to_stage(RenderStage::Queue, queue_bloom_bind_groups); // Add bloom to the 3d render graph @@ -69,28 +117,22 @@ impl Plugin for BloomPlugin { let draw_3d_graph = graph .get_sub_graph_mut(crate::core_3d::graph::NAME) .unwrap(); - draw_3d_graph.add_node(draw_3d_graph::node::BLOOM, bloom_node); - draw_3d_graph - .add_slot_edge( - draw_3d_graph.input_node().unwrap().id, - crate::core_3d::graph::input::VIEW_ENTITY, - draw_3d_graph::node::BLOOM, - BloomNode::IN_VIEW, - ) - .unwrap(); + draw_3d_graph.add_node(core_3d::graph::node::BLOOM, bloom_node); + draw_3d_graph.add_slot_edge( + draw_3d_graph.input_node().id, + crate::core_3d::graph::input::VIEW_ENTITY, + core_3d::graph::node::BLOOM, + BloomNode::IN_VIEW, + ); // MAIN_PASS -> BLOOM -> TONEMAPPING - draw_3d_graph - .add_node_edge( - crate::core_3d::graph::node::MAIN_PASS, - draw_3d_graph::node::BLOOM, - ) - .unwrap(); - draw_3d_graph - .add_node_edge( - draw_3d_graph::node::BLOOM, - crate::core_3d::graph::node::TONEMAPPING, - ) - .unwrap(); + draw_3d_graph.add_node_edge( + crate::core_3d::graph::node::MAIN_PASS, + core_3d::graph::node::BLOOM, + ); + draw_3d_graph.add_node_edge( + core_3d::graph::node::BLOOM, + crate::core_3d::graph::node::TONEMAPPING, + ); } // Add bloom to the 2d render graph @@ -100,87 +142,82 @@ impl Plugin for BloomPlugin { let draw_2d_graph = graph .get_sub_graph_mut(crate::core_2d::graph::NAME) .unwrap(); - draw_2d_graph.add_node(draw_2d_graph::node::BLOOM, bloom_node); - draw_2d_graph - .add_slot_edge( - draw_2d_graph.input_node().unwrap().id, - crate::core_2d::graph::input::VIEW_ENTITY, - draw_2d_graph::node::BLOOM, - BloomNode::IN_VIEW, - ) - .unwrap(); + draw_2d_graph.add_node(core_2d::graph::node::BLOOM, bloom_node); + draw_2d_graph.add_slot_edge( + draw_2d_graph.input_node().id, + crate::core_2d::graph::input::VIEW_ENTITY, + core_2d::graph::node::BLOOM, + BloomNode::IN_VIEW, + ); // MAIN_PASS -> BLOOM -> TONEMAPPING - draw_2d_graph - .add_node_edge( - crate::core_2d::graph::node::MAIN_PASS, - draw_2d_graph::node::BLOOM, - ) - .unwrap(); - draw_2d_graph - .add_node_edge( - draw_2d_graph::node::BLOOM, - crate::core_2d::graph::node::TONEMAPPING, - ) - .unwrap(); + draw_2d_graph.add_node_edge( + crate::core_2d::graph::node::MAIN_PASS, + core_2d::graph::node::BLOOM, + ); + draw_2d_graph.add_node_edge( + core_2d::graph::node::BLOOM, + crate::core_2d::graph::node::TONEMAPPING, + ); } } } -/// Applies a bloom effect to a HDR-enabled 2d or 3d camera. -/// -/// Bloom emulates an effect found in real cameras and the human eye, -/// causing halos to appear around very bright parts of the scene. -/// -/// Often used in conjunction with `bevy_pbr::StandardMaterial::emissive`. -/// -/// Bloom is best used alongside a tonemapping function that desaturates bright colors, -/// such as ACES Filmic (Bevy's default). -/// -/// See also . -#[derive(Component, Reflect, Clone)] -pub struct BloomSettings { - /// Intensity of the bloom effect (default: 0.04). - pub intensity: f32, - - /// Baseline of the quadratic threshold curve (default: 0.0). - /// - /// RGB values under the threshold curve will not have bloom applied. - /// Using a threshold is not physically accurate, but may fit better with your artistic direction. - pub threshold: f32, +impl ExtractComponent for BloomSettings { + type Query = (&'static Self, &'static Camera); - /// Controls how much to blend between the thresholded and non-thresholded colors (default: 0.5). - /// - /// 0.0 = Abrupt threshold, no blending - /// 1.0 = Fully soft threshold - /// - /// Values outside of the range [0.0, 1.0] will be clamped. - pub threshold_softness: f32, -} + type Filter = (); + type Out = BloomDownsamplingUniform; -impl Default for BloomSettings { - fn default() -> Self { - Self { - intensity: 0.04, - threshold: 0.0, - threshold_softness: 0.5, + fn extract_component((settings, camera): QueryItem<'_, Self::Query>) -> Option { + if !camera.is_active { + return None; + } + + if let (Some((origin, _)), Some(size), Some(target_size)) = ( + camera.physical_viewport_rect(), + camera.physical_viewport_size(), + camera.physical_target_size(), + ) { + // let min_view = size.x.min(size.y) / 2; + // let mip_count = calculate_mip_count(min_view); + // let scale = (min_view / 2u32.pow(mip_count)) as f32 / 8.0; + let pref_sett = &settings.prefilter_settings; + let knee = pref_sett.threshold * pref_sett.threshold_softness.clamp(0.0, 1.0); + + Some(BloomDownsamplingUniform { + threshold_precomputations: Vec4::new( + pref_sett.threshold, + pref_sett.threshold - knee, + 2.0 * knee, + 0.25 / (knee + 0.00001), + ), + viewport: UVec4::new(origin.x, origin.y, size.x, size.y).as_vec4() + / UVec4::new(target_size.x, target_size.y, target_size.x, target_size.y) + .as_vec4(), + }) + } else { + None } } } -struct BloomNode { +pub struct BloomNode { view_query: QueryState<( &'static ExtractedCamera, &'static ViewTarget, &'static BloomTexture, &'static BloomBindGroups, - &'static BloomUniformIndex, + &'static DynamicUniformIndex, + &'static BloomSettings, + &'static UpsamplingPipelineIds, + &'static BloomDownsamplingPipelineIds, )>, } impl BloomNode { - const IN_VIEW: &'static str = "view"; + pub const IN_VIEW: &'static str = "view"; - fn new(world: &mut World) -> Self { + pub fn new(world: &mut World) -> Self { Self { view_query: QueryState::new(world), } @@ -196,6 +233,9 @@ impl Node for BloomNode { self.view_query.update_archetypes(world); } + // Atypically for a post-processing effect, we do not + // use a secondary texture normally provided by view_target.post_process_write(), + // instead we write into our own bloom texture and then directly back onto main. fn run( &self, graph: &mut RenderGraphContext, @@ -206,76 +246,74 @@ impl Node for BloomNode { let _bloom_span = info_span!("bloom").entered(); let pipelines = world.resource::(); + let downsampling_pipeline_res = world.resource::(); let pipeline_cache = world.resource::(); - let bloom_uniforms = world.resource::(); + let uniforms = world.resource::>(); let view_entity = graph.get_input_entity(Self::IN_VIEW)?; let ( - Ok((camera, view_target, bloom_texture, bind_groups, uniform_index)), + camera, + view_target, + bloom_texture, + bind_groups, + uniform_index, + bloom_settings, + upsampling_pipeline_ids, + downsampling_pipeline_ids, + ) = match self.view_query.get_manual(world, view_entity) { + Ok(result) => result, + _ => return Ok(()), + }; + + // Downsampling pipelines + let ( Some(downsampling_first_pipeline), Some(downsampling_pipeline), + ) = ( + pipeline_cache.get_render_pipeline(downsampling_pipeline_ids.id_first), + pipeline_cache.get_render_pipeline(downsampling_pipeline_ids.id_main), + ) else { + return Ok(()); + }; + + // Upsampling pipleines + let ( Some(upsampling_pipeline), Some(upsampling_final_pipeline), - Some(bloom_uniforms), ) = ( - self.view_query.get_manual(world, view_entity), - pipeline_cache.get_render_pipeline(pipelines.downsampling_first_pipeline), - pipeline_cache.get_render_pipeline(pipelines.downsampling_pipeline), - pipeline_cache.get_render_pipeline(pipelines.upsampling_pipeline), - pipeline_cache.get_render_pipeline(pipelines.upsampling_final_pipeline), - bloom_uniforms.buffer.binding(), + pipeline_cache.get_render_pipeline(upsampling_pipeline_ids.id_main), + pipeline_cache.get_render_pipeline(upsampling_pipeline_ids.id_final), ) else { return Ok(()); }; - let view_target = view_target.post_process_write(); - - let downsampling_first_bind_group = - render_context - .render_device - .create_bind_group(&BindGroupDescriptor { - label: Some("bloom_downsampling_first_bind_group"), - layout: &pipelines.main_bind_group_layout, - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView(view_target.source), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Sampler(&pipelines.sampler), - }, - BindGroupEntry { - binding: 2, - resource: bloom_uniforms.clone(), - }, - ], - }); - let upsampling_final_bind_group = - render_context - .render_device - .create_bind_group(&BindGroupDescriptor { - label: Some("bloom_upsampling_final_bind_group"), - layout: &pipelines.upsampling_final_bind_group_layout, - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView(&bloom_texture.view(0)), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Sampler(&pipelines.sampler), - }, - BindGroupEntry { - binding: 2, - resource: bloom_uniforms, - }, - BindGroupEntry { - binding: 3, - resource: BindingResource::TextureView(view_target.source), - }, - ], - }); + // First downsample pass { + let downsampling_first_bind_group = { + let texture = BindGroupEntry { + binding: 0, + // We read from main texture directly + resource: BindingResource::TextureView(view_target.main_texture()), + }; + + let sampler = BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&pipelines.sampler), + }; + + let settings = BindGroupEntry { + binding: 2, + resource: uniforms.binding().unwrap().clone(), + }; + + render_context + .render_device + .create_bind_group(&BindGroupDescriptor { + label: Some("bloom_downsampling_first_bind_group"), + layout: &downsampling_pipeline_res.extended_bind_group_layout, + entries: &[texture, sampler, settings], + }) + }; + let view = &bloom_texture.view(0); let mut downsampling_first_pass = TrackedRenderPass::new(render_context.command_encoder.begin_render_pass( @@ -293,14 +331,12 @@ impl Node for BloomNode { downsampling_first_pass.set_bind_group( 0, &downsampling_first_bind_group, - &[uniform_index.0], + &[uniform_index.index()], ); - if let Some(viewport) = camera.viewport.as_ref() { - downsampling_first_pass.set_camera_viewport(viewport); - } downsampling_first_pass.draw(0..3, 0..1); } + // Other downsample passes for mip in 1..bloom_texture.mip_count { let view = &bloom_texture.view(mip); let mut downsampling_pass = @@ -319,14 +355,12 @@ impl Node for BloomNode { downsampling_pass.set_bind_group( 0, &bind_groups.downsampling_bind_groups[mip as usize - 1], - &[uniform_index.0], + &[], ); - if let Some(viewport) = camera.viewport.as_ref() { - downsampling_pass.set_camera_viewport(viewport); - } downsampling_pass.draw(0..3, 0..1); } + // Upsample passes except the final one for mip in (1..bloom_texture.mip_count).rev() { let view = &bloom_texture.view(mip - 1); let mut upsampling_pass = @@ -348,36 +382,45 @@ impl Node for BloomNode { upsampling_pass.set_bind_group( 0, &bind_groups.upsampling_bind_groups[(bloom_texture.mip_count - mip - 1) as usize], - &[uniform_index.0], + &[], ); - if let Some(viewport) = camera.viewport.as_ref() { - upsampling_pass.set_camera_viewport(viewport); - } + let blend = bloom_settings + .compute_blend_factor((mip) as f32, (bloom_texture.mip_count - 1) as f32); + upsampling_pass.set_blend_constant(Color::rgb_linear(blend, blend, blend)); upsampling_pass.draw(0..3, 0..1); } + // Final upsample pass + // This is very similar to the upsampling_pass above with the only difference + // being the pipeline (which itself is barely different) and the color attachment. + // Too bad. { let mut upsampling_final_pass = TrackedRenderPass::new(render_context.command_encoder.begin_render_pass( &RenderPassDescriptor { label: Some("bloom_upsampling_final_pass"), - color_attachments: &[Some(RenderPassColorAttachment { - view: view_target.destination, - resolve_target: None, - ops: Operations::default(), - })], + color_attachments: &[Some( + // We draw directly onto the main texture + view_target.get_unsampled_color_attachment(Operations { + load: LoadOp::Load, + store: true, + }), + )], depth_stencil_attachment: None, }, )); upsampling_final_pass.set_render_pipeline(upsampling_final_pipeline); upsampling_final_pass.set_bind_group( 0, - &upsampling_final_bind_group, - &[uniform_index.0], + &bind_groups.upsampling_bind_groups[(bloom_texture.mip_count - 1) as usize], + &[], ); if let Some(viewport) = camera.viewport.as_ref() { upsampling_final_pass.set_camera_viewport(viewport); } + let blend = + bloom_settings.compute_blend_factor(0.0, (bloom_texture.mip_count - 1) as f32); + upsampling_final_pass.set_blend_constant(Color::rgb_linear(blend, blend, blend)); upsampling_final_pass.draw(0..3, 0..1); } @@ -387,14 +430,6 @@ impl Node for BloomNode { #[derive(Resource)] struct BloomPipelines { - downsampling_first_pipeline: CachedRenderPipelineId, - downsampling_pipeline: CachedRenderPipelineId, - upsampling_pipeline: CachedRenderPipelineId, - upsampling_final_pipeline: CachedRenderPipelineId, - - main_bind_group_layout: BindGroupLayout, - upsampling_final_bind_group_layout: BindGroupLayout, - sampler: Sampler, } @@ -410,188 +445,7 @@ impl FromWorld for BloomPipelines { ..Default::default() }); - let main_bind_group_layout = - render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - label: Some("bloom_main_bind_group_layout"), - entries: &[ - // Input texture (higher resolution for downsample, lower resolution for upsample) - BindGroupLayoutEntry { - binding: 0, - ty: BindingType::Texture { - sample_type: TextureSampleType::Float { filterable: true }, - view_dimension: TextureViewDimension::D2, - multisampled: false, - }, - visibility: ShaderStages::FRAGMENT, - count: None, - }, - // Sampler - BindGroupLayoutEntry { - binding: 1, - ty: BindingType::Sampler(SamplerBindingType::Filtering), - visibility: ShaderStages::FRAGMENT, - count: None, - }, - // Bloom settings - BindGroupLayoutEntry { - binding: 2, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: true, - min_binding_size: Some(BloomUniform::min_size()), - }, - visibility: ShaderStages::FRAGMENT, - count: None, - }, - ], - }); - - let upsampling_final_bind_group_layout = - render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - label: Some("bloom_upsampling_final_bind_group_layout"), - entries: &[ - // Main pass texture - BindGroupLayoutEntry { - binding: 0, - ty: BindingType::Texture { - sample_type: TextureSampleType::Float { filterable: true }, - view_dimension: TextureViewDimension::D2, - multisampled: false, - }, - visibility: ShaderStages::FRAGMENT, - count: None, - }, - // Sampler - BindGroupLayoutEntry { - binding: 1, - ty: BindingType::Sampler(SamplerBindingType::Filtering), - visibility: ShaderStages::FRAGMENT, - count: None, - }, - // Bloom settings - BindGroupLayoutEntry { - binding: 2, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: true, - min_binding_size: Some(BloomUniform::min_size()), - }, - visibility: ShaderStages::FRAGMENT, - count: None, - }, - // Second to last upsample result texture (BloomTexture mip 0) - BindGroupLayoutEntry { - binding: 3, - ty: BindingType::Texture { - sample_type: TextureSampleType::Float { filterable: true }, - view_dimension: TextureViewDimension::D2, - multisampled: false, - }, - visibility: ShaderStages::FRAGMENT, - count: None, - }, - ], - }); - - let mut pipeline_cache = world.resource_mut::(); - - let downsampling_first_pipeline = - pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor { - label: Some("bloom_downsampling_first_pipeline".into()), - layout: Some(vec![main_bind_group_layout.clone()]), - vertex: fullscreen_shader_vertex_state(), - fragment: Some(FragmentState { - shader: BLOOM_SHADER_HANDLE.typed::(), - shader_defs: vec!["FIRST_DOWNSAMPLE".into()], - entry_point: "downsample_first".into(), - targets: vec![Some(ColorTargetState { - format: TextureFormat::Rg11b10Float, - blend: None, - write_mask: ColorWrites::ALL, - })], - }), - primitive: PrimitiveState::default(), - depth_stencil: None, - multisample: MultisampleState::default(), - }); - - let downsampling_pipeline = - pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor { - label: Some("bloom_downsampling_pipeline".into()), - layout: Some(vec![main_bind_group_layout.clone()]), - vertex: fullscreen_shader_vertex_state(), - fragment: Some(FragmentState { - shader: BLOOM_SHADER_HANDLE.typed::(), - shader_defs: vec![], - entry_point: "downsample".into(), - targets: vec![Some(ColorTargetState { - format: TextureFormat::Rg11b10Float, - blend: None, - write_mask: ColorWrites::ALL, - })], - }), - primitive: PrimitiveState::default(), - depth_stencil: None, - multisample: MultisampleState::default(), - }); - - let upsampling_pipeline = pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor { - label: Some("bloom_upsampling_pipeline".into()), - layout: Some(vec![main_bind_group_layout.clone()]), - vertex: fullscreen_shader_vertex_state(), - fragment: Some(FragmentState { - shader: BLOOM_SHADER_HANDLE.typed::(), - shader_defs: vec![], - entry_point: "upsample".into(), - targets: vec![Some(ColorTargetState { - format: TextureFormat::Rg11b10Float, - blend: Some(BlendState { - color: BlendComponent { - src_factor: BlendFactor::One, - dst_factor: BlendFactor::One, - operation: BlendOperation::Add, - }, - alpha: BlendComponent::REPLACE, - }), - write_mask: ColorWrites::ALL, - })], - }), - primitive: PrimitiveState::default(), - depth_stencil: None, - multisample: MultisampleState::default(), - }); - - let upsampling_final_pipeline = - pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor { - label: Some("bloom_upsampling_final_pipeline".into()), - layout: Some(vec![upsampling_final_bind_group_layout.clone()]), - vertex: fullscreen_shader_vertex_state(), - fragment: Some(FragmentState { - shader: BLOOM_SHADER_HANDLE.typed::(), - shader_defs: vec![], - entry_point: "upsample_final".into(), - targets: vec![Some(ColorTargetState { - format: ViewTarget::TEXTURE_FORMAT_HDR, - blend: None, - write_mask: ColorWrites::ALL, - })], - }), - primitive: PrimitiveState::default(), - depth_stencil: None, - multisample: MultisampleState::default(), - }); - - BloomPipelines { - downsampling_first_pipeline, - downsampling_pipeline, - upsampling_pipeline, - upsampling_final_pipeline, - - sampler, - - main_bind_group_layout, - upsampling_final_bind_group_layout, - } + BloomPipelines { sampler } } } @@ -600,7 +454,7 @@ fn extract_bloom_settings( cameras: Extract>>, ) { for (entity, camera, bloom_settings) in &cameras { - if camera.is_active && camera.hdr { + if camera.is_active { commands.get_or_spawn(entity).insert(bloom_settings.clone()); } } @@ -627,7 +481,7 @@ fn prepare_bloom_textures( mut commands: Commands, mut texture_cache: ResMut, render_device: Res, - views: Query<(Entity, &ExtractedCamera), With>, + views: Query<(Entity, &ExtractedCamera), With>, ) { let mut textures = HashMap::default(); for (entity, camera) in &views { @@ -637,8 +491,8 @@ fn prepare_bloom_textures( }) = camera.physical_viewport_size { let min_view = width.min(height) as f32; - // How many times we can halve the resolution, minus 3 to avoid tiny mips - let mip_count = (min_view.log2().round() as i32 - 3).max(1) as u32; + // How many times we can halve the resolution minus one so we don't go unnecessarily low + let mip_count = (min_view.log2().floor() as u32 - 1).max(1); let texture_descriptor = TextureDescriptor { label: Some("bloom_texture"), @@ -650,7 +504,7 @@ fn prepare_bloom_textures( mip_level_count: mip_count, sample_count: 1, dimension: TextureDimension::D2, - format: TextureFormat::Rg11b10Float, + format: BLOOM_TEXTURE_FORMAT, usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING, }; @@ -666,55 +520,6 @@ fn prepare_bloom_textures( } } -#[derive(ShaderType)] -struct BloomUniform { - intensity: f32, - // Precomputed values used when thresholding, see https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/#3.4 - threshold_precomputations: Vec4, -} - -#[derive(Resource, Default)] -struct BloomUniforms { - buffer: DynamicUniformBuffer, -} - -#[derive(Component)] -struct BloomUniformIndex(u32); - -fn prepare_bloom_uniforms( - mut commands: Commands, - render_device: Res, - render_queue: Res, - mut bloom_uniforms: ResMut, - bloom_query: Query<(Entity, &BloomSettings)>, -) { - bloom_uniforms.buffer.clear(); - - let entities = bloom_query - .iter() - .map(|(entity, settings)| { - let knee = settings.threshold * settings.threshold_softness.clamp(0.0, 1.0); - let uniform = BloomUniform { - intensity: settings.intensity, - threshold_precomputations: Vec4::new( - settings.threshold, - settings.threshold - knee, - 2.0 * knee, - 0.25 / (knee + 0.00001), - ), - }; - - let index = bloom_uniforms.buffer.push(uniform); - (entity, (BloomUniformIndex(index))) - }) - .collect::>(); - commands.insert_or_spawn_batch(entities); - - bloom_uniforms - .buffer - .write_buffer(&render_device, &render_queue); -} - #[derive(Component)] struct BloomBindGroups { downsampling_bind_groups: Box<[BindGroup]>, @@ -725,65 +530,57 @@ fn queue_bloom_bind_groups( mut commands: Commands, render_device: Res, pipelines: Res, - bloom_uniforms: Res, + downsampling_pipeline: Res, + upsampling_pipeline: Res, + // bloom_uniforms: Res, views: Query<(Entity, &BloomTexture)>, ) { - if let Some(bloom_uniforms) = bloom_uniforms.buffer.binding() { - for (entity, bloom_texture) in &views { - let bind_group_count = bloom_texture.mip_count as usize - 1; - - let mut downsampling_bind_groups = Vec::with_capacity(bind_group_count); - for mip in 1..bloom_texture.mip_count { - let bind_group = render_device.create_bind_group(&BindGroupDescriptor { - label: Some("bloom_downsampling_bind_group"), - layout: &pipelines.main_bind_group_layout, - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView(&bloom_texture.view(mip - 1)), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Sampler(&pipelines.sampler), - }, - BindGroupEntry { - binding: 2, - resource: bloom_uniforms.clone(), - }, - ], - }); - - downsampling_bind_groups.push(bind_group); - } + for (entity, bloom_texture) in &views { + let bind_group_count = bloom_texture.mip_count as usize - 1; - let mut upsampling_bind_groups = Vec::with_capacity(bind_group_count); - for mip in (1..bloom_texture.mip_count).rev() { - let bind_group = render_device.create_bind_group(&BindGroupDescriptor { - label: Some("bloom_upsampling_bind_group"), - layout: &pipelines.main_bind_group_layout, - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView(&bloom_texture.view(mip)), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Sampler(&pipelines.sampler), - }, - BindGroupEntry { - binding: 2, - resource: bloom_uniforms.clone(), - }, - ], - }); - - upsampling_bind_groups.push(bind_group); - } + let mut downsampling_bind_groups = Vec::with_capacity(bind_group_count); + for mip in 1..bloom_texture.mip_count { + let bind_group = render_device.create_bind_group(&BindGroupDescriptor { + label: Some("bloom_downsampling_bind_group"), + layout: &downsampling_pipeline.bind_group_layout, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&bloom_texture.view(mip - 1)), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&pipelines.sampler), + }, + ], + }); + + downsampling_bind_groups.push(bind_group); + } - commands.entity(entity).insert(BloomBindGroups { - downsampling_bind_groups: downsampling_bind_groups.into_boxed_slice(), - upsampling_bind_groups: upsampling_bind_groups.into_boxed_slice(), + let mut upsampling_bind_groups = Vec::with_capacity(bind_group_count); + for mip in (0..bloom_texture.mip_count).rev() { + let bind_group = render_device.create_bind_group(&BindGroupDescriptor { + label: Some("bloom_upsampling_bind_group"), + layout: &upsampling_pipeline.bind_group_layout, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&bloom_texture.view(mip)), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&pipelines.sampler), + }, + ], }); + + upsampling_bind_groups.push(bind_group); } + + commands.entity(entity).insert(BloomBindGroups { + downsampling_bind_groups: downsampling_bind_groups.into_boxed_slice(), + upsampling_bind_groups: upsampling_bind_groups.into_boxed_slice(), + }); } } diff --git a/crates/bevy_core_pipeline/src/bloom/settings.rs b/crates/bevy_core_pipeline/src/bloom/settings.rs new file mode 100644 index 0000000000000..3032992d6fe5c --- /dev/null +++ b/crates/bevy_core_pipeline/src/bloom/settings.rs @@ -0,0 +1,168 @@ +use bevy_ecs::prelude::Component; +use bevy_reflect::Reflect; + +/// Applies a bloom effect to a 2d or 3d camera. +/// +/// Bloom emulates an effect found in real cameras and the human eye, +/// causing halos to appear around very bright parts of the scene. +/// +/// Often used in conjunction with `bevy_pbr::StandardMaterial::emissive`. +/// +/// Bloom is best used alongside a tonemapping function that desaturates bright colors, +/// such as ACES Filmic (Bevy's default). +/// +/// See also . +/// +/// Bevy's implementation uses a parametric curve to blend between a set of +/// blurred (lower freequency) images generated from input image. +/// See for a vizualization of the parametric curve +/// used in Bevy as well as a vislualization of the curve's respective scattering profile. +#[derive(Component, Reflect, Clone)] +pub struct BloomSettings { + /// Controls the baseline of how much the image is scattered (default: 0.3). + /// + /// # In energy-conserving mode + /// The value represents how likely the light is to scatter. + /// + /// The value should be clamed between 0.0 and 1.0 where: + /// * 0.0 means no bloom + /// * 1.0 means the light is scattered as much as possible + /// + /// # In additive mode + /// The value represents how much scattered light is added to + /// the image to create the glow effect. + /// + /// In this configuration: + /// * 0.0 means no bloom + /// * > 0.0 means a propotrionate amount of scattered light is added + pub intensity: f32, + + /// Low frequency contribution boost. + /// Controls how much more likely the light + /// is to scatter completely sideways (low frequency image). + /// + /// Comparable to a low shelf boost on an equalizer. + /// + /// # In energy-conserving mode + /// The value should be clamed between 0.0 and 1.0 where: + /// * 0.0 means low frequency light uses base intensity for blend factor calculation + /// * 1.0 means low frequency light contributes at full power + /// + /// # In additive mode + /// The value represents how much scattered light is added to + /// the image to create the glow effect. + /// + /// In this configuration: + /// * 0.0 means no bloom + /// * > 0.0 means a propotrionate amount of scattered light is added + pub lf_boost: f32, + + /// Low frequency contribution boost curve. + /// Controls the curvature of the blend factor function + /// making frequncies next to lowest one contribute more. + /// + /// Somewhat comparable to the Q factor of an equalizer node. + /// + /// Valid range: + /// * 0.0 - base base intensity and boosted intensity are lineraly interpolated + /// * 1.0 - all frequencies below maximum are at boosted intensity level + pub lf_boost_curvature: f32, + + /// Tightens how much the light scatters (default: 1.0). + /// + /// Valid range: + /// * 0.0 - maximum scattering angle is 0deg (no scattering) + /// * 1.0 - maximum scattering angle is 90deg + pub high_pass_frequency: f32, + + pub prefilter_settings: BloomPrefilterSettings, + + /// Controls whether bloom textures + /// are blended between or added to each other. Useful + /// if image brightening is desired and a must-change + /// if prefilter_settings are used. + /// + /// # Recommendation + /// Set to Additive if prefilter_settings are + /// configured in a non-energy-conserving way, + /// otherwise set to EnergyConserving. + pub composite_mode: BloomCompositeMode, +} + +impl BloomSettings { + /// Recommended for HDR rendering + pub const NATURAL: Self = Self { + intensity: 0.3, + lf_boost: 0.7, + lf_boost_curvature: 0.95, + high_pass_frequency: 1.0, + prefilter_settings: BloomPrefilterSettings { + threshold: 0.0, + threshold_softness: 0.0, + }, + composite_mode: BloomCompositeMode::EnergyConserving, + }; + + /// Recommended for SDR rendering + pub const OLD_SCHOOL: Self = Self { + intensity: 0.05, + lf_boost: 0.7, + lf_boost_curvature: 0.95, + high_pass_frequency: 1.0, + prefilter_settings: BloomPrefilterSettings { + threshold: 0.6, + threshold_softness: 0.2, + }, + composite_mode: BloomCompositeMode::Additive, + }; + + pub const SCREEN_BLUR: Self = Self { + intensity: 1.0, + lf_boost: 0.0, + lf_boost_curvature: 0.0, + high_pass_frequency: 1.0 / 3.0, + prefilter_settings: BloomPrefilterSettings { + threshold: 0.0, + threshold_softness: 0.0, + }, + composite_mode: BloomCompositeMode::EnergyConserving, + }; +} + +impl Default for BloomSettings { + fn default() -> Self { + Self::NATURAL + } +} + +/// Applies a threshold filter to the input image to +/// extract the brightest regions before blurring them and compositing +/// back onto the original image. These settings are useful if bloom is applied +/// to an SDR image or when emulating the 1990s-2000s game look. +/// +/// # Considerations +/// * It is recommended to use this only if HDR rendering is not possible. +/// * Changing these settings creates a physically inaccurate image. +/// * Changing these settings makes it easy to make the final result look worse. +/// * Non-default prefilter settings should be used in conjuction with composite_mode::Additive +#[derive(Default, Clone, Reflect)] +pub struct BloomPrefilterSettings { + /// Baseline of the quadratic threshold curve (default: 0.0). + /// + /// RGB values under the threshold curve will not contribute to the effect. + pub threshold: f32, + + /// Controls how much to blend between the thresholded and non-thresholded colors (default: 0.0). + /// + /// 0.0 = Abrupt threshold, no blending + /// 1.0 = Fully soft threshold + /// + /// Values outside of the range [0.0, 1.0] will be clamped. + pub threshold_softness: f32, +} + +#[derive(Clone, Reflect, PartialEq, Eq, Hash, Copy)] +pub enum BloomCompositeMode { + EnergyConserving, + Additive, +} diff --git a/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs new file mode 100644 index 0000000000000..f634c92a6dfa9 --- /dev/null +++ b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs @@ -0,0 +1,184 @@ +use bevy_ecs::{ + prelude::{Component, Entity}, + query::With, + system::{Commands, Query, Res, ResMut, Resource}, + world::{FromWorld, World}, +}; +use bevy_render::{ + render_resource::{ + BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, + BlendComponent, BlendFactor, BlendOperation, BlendState, CachedRenderPipelineId, + ColorTargetState, ColorWrites, FragmentState, MultisampleState, PipelineCache, + PrimitiveState, RenderPipelineDescriptor, SamplerBindingType, Shader, ShaderStages, + SpecializedRenderPipeline, SpecializedRenderPipelines, TextureFormat, TextureSampleType, + TextureViewDimension, + }, + renderer::RenderDevice, + texture::BevyDefault, + view::{ExtractedView, ViewTarget}, +}; + +use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; + +use super::{BloomCompositeMode, BloomSettings, BLOOM_SHADER_HANDLE, BLOOM_TEXTURE_FORMAT}; + +#[derive(Component)] +pub struct UpsamplingPipelineIds { + pub id_main: CachedRenderPipelineId, + pub id_final: CachedRenderPipelineId, +} + +#[derive(Resource)] +pub struct BloomUpsamplingPipeline { + pub bind_group_layout: BindGroupLayout, +} + +#[derive(PartialEq, Eq, Hash, Clone)] +pub struct BloomUpsamplingPipelineKeys { + hdr: bool, + composite_mode: BloomCompositeMode, + final_pipeline: bool, +} + +impl FromWorld for BloomUpsamplingPipeline { + fn from_world(world: &mut World) -> Self { + let render_device = world.resource::(); + + let bind_group_layout = + render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("bloom_upsampling_bind_group_layout"), + entries: &[ + // Input texture + BindGroupLayoutEntry { + binding: 0, + ty: BindingType::Texture { + sample_type: TextureSampleType::Float { filterable: true }, + view_dimension: TextureViewDimension::D2, + multisampled: false, + }, + visibility: ShaderStages::FRAGMENT, + count: None, + }, + // Sampler + BindGroupLayoutEntry { + binding: 1, + ty: BindingType::Sampler(SamplerBindingType::Filtering), + visibility: ShaderStages::FRAGMENT, + count: None, + }, + ], + }); + + BloomUpsamplingPipeline { + // sampler, + bind_group_layout, + } + } +} + +impl SpecializedRenderPipeline for BloomUpsamplingPipeline { + type Key = BloomUpsamplingPipelineKeys; + + fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { + let texture_format = if key.final_pipeline { + if key.hdr { + ViewTarget::TEXTURE_FORMAT_HDR + } else { + TextureFormat::bevy_default() + } + } else { + BLOOM_TEXTURE_FORMAT + }; + + RenderPipelineDescriptor { + label: Some("bloom_upsampling_pipeline".into()), + layout: Some(vec![self.bind_group_layout.clone()]), + vertex: fullscreen_shader_vertex_state(), + fragment: Some(FragmentState { + shader: BLOOM_SHADER_HANDLE.typed::(), + shader_defs: vec![], + entry_point: "upsample".into(), + targets: vec![Some(ColorTargetState { + format: texture_format, + blend: Some(BlendState { + color: match key.composite_mode { + BloomCompositeMode::EnergyConserving => { + // At the time of developing this we decided to blend our + // blur pyramid levels using native WGPU render pass blend + // constants. They are set in the bloom node's run function. + // This seemed like a good approach at the time which allowed + // us to perform complex calculations for blend levels on the CPU, + // however, we missed the fact that this prevented us from using + // textures to customize bloom apperance on individual parts + // of the screen and create effects such as lens dirt or + // screen blur behind certain UI elements. + // + // TODO: Use alpha instead of blend constants and move + // compute_blend_factor to the shader. The shader + // will likely need to know current mip number or + // mip "angle" (original texture is 0deg, max mip is 90deg) + // so make sure you give it that as a uniform. + // That does have to be provided per each pass unlike other + // uniforms that are set once. + BlendComponent { + src_factor: BlendFactor::Constant, + dst_factor: BlendFactor::OneMinusConstant, + operation: BlendOperation::Add, + } + } + BloomCompositeMode::Additive => BlendComponent { + src_factor: BlendFactor::Constant, + dst_factor: BlendFactor::One, + operation: BlendOperation::Add, + }, + }, + alpha: BlendComponent { + src_factor: BlendFactor::Zero, + dst_factor: BlendFactor::One, + operation: BlendOperation::Add, + }, + }), + write_mask: ColorWrites::ALL, + })], + }), + primitive: PrimitiveState::default(), + depth_stencil: None, + multisample: MultisampleState::default(), + } + } +} + +pub fn prepare_upsampling_pipeline( + mut commands: Commands, + mut pipeline_cache: ResMut, + mut pipelines: ResMut>, + pipeline: Res, + views: Query<(Entity, &ExtractedView, &BloomSettings), With>, +) { + for (entity, view, settings) in &views { + let pipeline_id = pipelines.specialize( + &mut pipeline_cache, + &pipeline, + BloomUpsamplingPipelineKeys { + hdr: view.hdr, + composite_mode: settings.composite_mode, + final_pipeline: false, + }, + ); + + let pipeline_final_id = pipelines.specialize( + &mut pipeline_cache, + &pipeline, + BloomUpsamplingPipelineKeys { + hdr: view.hdr, + composite_mode: settings.composite_mode, + final_pipeline: true, + }, + ); + + commands.entity(entity).insert(UpsamplingPipelineIds { + id_main: pipeline_id, + id_final: pipeline_final_id, + }); + } +} diff --git a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs index 86906240b467c..1f52beeec8aef 100644 --- a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs +++ b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs @@ -18,9 +18,10 @@ pub struct Camera2d { impl ExtractComponent for Camera2d { type Query = &'static Self; type Filter = With; + type Out = Self; - fn extract_component(item: QueryItem<'_, Self::Query>) -> Self { - item.clone() + fn extract_component(item: QueryItem<'_, Self::Query>) -> Option { + Some(item.clone()) } } 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 f4006822f3436..2a262b426195b 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 64362f722e59e..49636f267c8a5 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -8,7 +8,9 @@ pub mod graph { } pub mod node { pub const MAIN_PASS: &str = "main_pass"; + pub const BLOOM: &str = "bloom"; pub const TONEMAPPING: &str = "tonemapping"; + pub const FXAA: &str = "fxaa"; pub const UPSCALING: &str = "upscaling"; pub const END_MAIN_PASS_POST_PROCESSING: &str = "end_main_pass_post_processing"; } @@ -70,45 +72,33 @@ impl Plugin for Core2dPlugin { graph::input::VIEW_ENTITY, SlotType::Entity, )]); - draw_2d_graph - .add_slot_edge( - input_node_id, - graph::input::VIEW_ENTITY, - graph::node::MAIN_PASS, - MainPass2dNode::IN_VIEW, - ) - .unwrap(); - draw_2d_graph - .add_slot_edge( - input_node_id, - graph::input::VIEW_ENTITY, - graph::node::TONEMAPPING, - TonemappingNode::IN_VIEW, - ) - .unwrap(); - draw_2d_graph - .add_slot_edge( - input_node_id, - graph::input::VIEW_ENTITY, - graph::node::UPSCALING, - UpscalingNode::IN_VIEW, - ) - .unwrap(); - draw_2d_graph - .add_node_edge(graph::node::MAIN_PASS, graph::node::TONEMAPPING) - .unwrap(); - draw_2d_graph - .add_node_edge( - graph::node::TONEMAPPING, - graph::node::END_MAIN_PASS_POST_PROCESSING, - ) - .unwrap(); - draw_2d_graph - .add_node_edge( - graph::node::END_MAIN_PASS_POST_PROCESSING, - graph::node::UPSCALING, - ) - .unwrap(); + draw_2d_graph.add_slot_edge( + input_node_id, + graph::input::VIEW_ENTITY, + graph::node::MAIN_PASS, + MainPass2dNode::IN_VIEW, + ); + draw_2d_graph.add_slot_edge( + input_node_id, + graph::input::VIEW_ENTITY, + graph::node::TONEMAPPING, + TonemappingNode::IN_VIEW, + ); + draw_2d_graph.add_slot_edge( + input_node_id, + graph::input::VIEW_ENTITY, + graph::node::UPSCALING, + UpscalingNode::IN_VIEW, + ); + draw_2d_graph.add_node_edge(graph::node::MAIN_PASS, graph::node::TONEMAPPING); + draw_2d_graph.add_node_edge( + graph::node::TONEMAPPING, + graph::node::END_MAIN_PASS_POST_PROCESSING, + ); + draw_2d_graph.add_node_edge( + graph::node::END_MAIN_PASS_POST_PROCESSING, + graph::node::UPSCALING, + ); graph.add_sub_graph(graph::NAME, draw_2d_graph); } } diff --git a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs index 057f45c53a72c..d722f411a21d6 100644 --- a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs +++ b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs @@ -50,9 +50,10 @@ impl From for LoadOp { impl ExtractComponent for Camera3d { type Query = &'static Self; type Filter = With; + type Out = Self; - fn extract_component(item: QueryItem<'_, Self::Query>) -> Self { - item.clone() + fn extract_component(item: QueryItem<'_, Self::Query>) -> Option { + Some(item.clone()) } } 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 cf3a3d38fa603..1067643e03b12 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 893c1dfe4fbb1..8dc51d760ac1c 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -8,7 +8,9 @@ pub mod graph { } pub mod node { pub const MAIN_PASS: &str = "main_pass"; + pub const BLOOM: &str = "bloom"; pub const TONEMAPPING: &str = "tonemapping"; + pub const FXAA: &str = "fxaa"; pub const UPSCALING: &str = "upscaling"; pub const END_MAIN_PASS_POST_PROCESSING: &str = "end_main_pass_post_processing"; } @@ -80,45 +82,33 @@ impl Plugin for Core3dPlugin { graph::input::VIEW_ENTITY, SlotType::Entity, )]); - draw_3d_graph - .add_slot_edge( - input_node_id, - graph::input::VIEW_ENTITY, - graph::node::MAIN_PASS, - MainPass3dNode::IN_VIEW, - ) - .unwrap(); - draw_3d_graph - .add_slot_edge( - input_node_id, - graph::input::VIEW_ENTITY, - graph::node::TONEMAPPING, - TonemappingNode::IN_VIEW, - ) - .unwrap(); - draw_3d_graph - .add_slot_edge( - input_node_id, - graph::input::VIEW_ENTITY, - graph::node::UPSCALING, - UpscalingNode::IN_VIEW, - ) - .unwrap(); - draw_3d_graph - .add_node_edge(graph::node::MAIN_PASS, graph::node::TONEMAPPING) - .unwrap(); - draw_3d_graph - .add_node_edge( - graph::node::TONEMAPPING, - graph::node::END_MAIN_PASS_POST_PROCESSING, - ) - .unwrap(); - draw_3d_graph - .add_node_edge( - graph::node::END_MAIN_PASS_POST_PROCESSING, - graph::node::UPSCALING, - ) - .unwrap(); + draw_3d_graph.add_slot_edge( + input_node_id, + graph::input::VIEW_ENTITY, + graph::node::MAIN_PASS, + MainPass3dNode::IN_VIEW, + ); + draw_3d_graph.add_slot_edge( + input_node_id, + graph::input::VIEW_ENTITY, + graph::node::TONEMAPPING, + TonemappingNode::IN_VIEW, + ); + draw_3d_graph.add_slot_edge( + input_node_id, + graph::input::VIEW_ENTITY, + graph::node::UPSCALING, + UpscalingNode::IN_VIEW, + ); + draw_3d_graph.add_node_edge(graph::node::MAIN_PASS, graph::node::TONEMAPPING); + draw_3d_graph.add_node_edge( + graph::node::TONEMAPPING, + graph::node::END_MAIN_PASS_POST_PROCESSING, + ); + draw_3d_graph.add_node_edge( + graph::node::END_MAIN_PASS_POST_PROCESSING, + graph::node::UPSCALING, + ); graph.add_sub_graph(graph::NAME, draw_3d_graph); } } diff --git a/crates/bevy_core_pipeline/src/fxaa/mod.rs b/crates/bevy_core_pipeline/src/fxaa/mod.rs index 02d4d0ca257e5..f49425e7250c4 100644 --- a/crates/bevy_core_pipeline/src/fxaa/mod.rs +++ b/crates/bevy_core_pipeline/src/fxaa/mod.rs @@ -1,9 +1,4 @@ -mod node; - -use crate::{ - core_2d, core_3d, fullscreen_vertex_shader::fullscreen_shader_vertex_state, - fxaa::node::FxaaNode, -}; +use crate::{core_2d, core_3d, fullscreen_vertex_shader::fullscreen_shader_vertex_state}; use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, HandleUntyped}; use bevy_derive::Deref; @@ -20,6 +15,10 @@ use bevy_render::{ RenderApp, RenderStage, }; +mod node; + +pub use node::FxaaNode; + #[derive(Eq, PartialEq, Hash, Clone, Copy)] pub enum Sensitivity { Low, @@ -70,18 +69,16 @@ impl Default for Fxaa { impl ExtractComponent for Fxaa { type Query = &'static Self; type Filter = With; + type Out = Self; - fn extract_component(item: QueryItem) -> Self { - item.clone() + fn extract_component(item: QueryItem) -> Option { + Some(item.clone()) } } const FXAA_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 4182761465141723543); -pub const FXAA_NODE_3D: &str = "fxaa_node_3d"; -pub const FXAA_NODE_2D: &str = "fxaa_node_2d"; - /// Adds support for Fast Approximate Anti-Aliasing (FXAA) pub struct FxaaPlugin; impl Plugin for FxaaPlugin { @@ -104,52 +101,46 @@ impl Plugin for FxaaPlugin { let mut binding = render_app.world.resource_mut::(); let graph = binding.get_sub_graph_mut(core_3d::graph::NAME).unwrap(); - graph.add_node(FXAA_NODE_3D, fxaa_node); - - graph - .add_slot_edge( - graph.input_node().unwrap().id, - core_3d::graph::input::VIEW_ENTITY, - FXAA_NODE_3D, - FxaaNode::IN_VIEW, - ) - .unwrap(); - - graph - .add_node_edge(core_3d::graph::node::TONEMAPPING, FXAA_NODE_3D) - .unwrap(); - graph - .add_node_edge( - FXAA_NODE_3D, - core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING, - ) - .unwrap(); + graph.add_node(core_3d::graph::node::FXAA, fxaa_node); + + graph.add_slot_edge( + graph.input_node().id, + core_3d::graph::input::VIEW_ENTITY, + core_3d::graph::node::FXAA, + FxaaNode::IN_VIEW, + ); + + graph.add_node_edge( + core_3d::graph::node::TONEMAPPING, + core_3d::graph::node::FXAA, + ); + graph.add_node_edge( + core_3d::graph::node::FXAA, + core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING, + ); } { let fxaa_node = FxaaNode::new(&mut render_app.world); let mut binding = render_app.world.resource_mut::(); let graph = binding.get_sub_graph_mut(core_2d::graph::NAME).unwrap(); - graph.add_node(FXAA_NODE_2D, fxaa_node); - - graph - .add_slot_edge( - graph.input_node().unwrap().id, - core_2d::graph::input::VIEW_ENTITY, - FXAA_NODE_2D, - FxaaNode::IN_VIEW, - ) - .unwrap(); - - graph - .add_node_edge(core_2d::graph::node::TONEMAPPING, FXAA_NODE_2D) - .unwrap(); - graph - .add_node_edge( - FXAA_NODE_2D, - core_2d::graph::node::END_MAIN_PASS_POST_PROCESSING, - ) - .unwrap(); + graph.add_node(core_2d::graph::node::FXAA, fxaa_node); + + graph.add_slot_edge( + graph.input_node().id, + core_2d::graph::input::VIEW_ENTITY, + core_2d::graph::node::FXAA, + FxaaNode::IN_VIEW, + ); + + graph.add_node_edge( + core_2d::graph::node::TONEMAPPING, + core_2d::graph::node::FXAA, + ); + graph.add_node_edge( + core_2d::graph::node::FXAA, + core_2d::graph::node::END_MAIN_PASS_POST_PROCESSING, + ); } } } @@ -212,8 +203,8 @@ impl SpecializedRenderPipeline for FxaaPipeline { fragment: Some(FragmentState { shader: FXAA_SHADER_HANDLE.typed(), shader_defs: vec![ - format!("EDGE_THRESH_{}", key.edge_threshold.get_str()), - format!("EDGE_THRESH_MIN_{}", key.edge_threshold_min.get_str()), + format!("EDGE_THRESH_{}", key.edge_threshold.get_str()).into(), + format!("EDGE_THRESH_MIN_{}", key.edge_threshold_min.get_str()).into(), ], entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index 3b417e8c82af9..785c231a43fb9 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -1,7 +1,3 @@ -mod node; - -pub use node::TonemappingNode; - use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, HandleUntyped}; @@ -14,6 +10,10 @@ use bevy_render::renderer::RenderDevice; use bevy_render::view::ViewTarget; use bevy_render::{render_resource::*, RenderApp, RenderStage}; +mod node; + +pub use node::TonemappingNode; + const TONEMAPPING_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 17015368199668024512); @@ -66,7 +66,7 @@ impl SpecializedRenderPipeline for TonemappingPipeline { fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { let mut shader_defs = Vec::new(); if key.deband_dither { - shader_defs.push("DEBAND_DITHER".to_string()); + shader_defs.push("DEBAND_DITHER".into()); } RenderPipelineDescriptor { label: Some("tonemapping pipeline".into()), @@ -164,8 +164,9 @@ impl Tonemapping { impl ExtractComponent for Tonemapping { type Query = &'static Self; type Filter = With; + type Out = Self; - fn extract_component(item: QueryItem) -> Self { - item.clone() + fn extract_component(item: QueryItem) -> Option { + Some(item.clone()) } } diff --git a/crates/bevy_core_pipeline/src/upscaling/mod.rs b/crates/bevy_core_pipeline/src/upscaling/mod.rs index 139d060eaf7c7..b8fa9718d000f 100644 --- a/crates/bevy_core_pipeline/src/upscaling/mod.rs +++ b/crates/bevy_core_pipeline/src/upscaling/mod.rs @@ -1,17 +1,15 @@ -mod node; - -pub use node::UpscalingNode; - +use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, HandleUntyped}; use bevy_ecs::prelude::*; +use bevy_reflect::TypeUuid; use bevy_render::renderer::RenderDevice; use bevy_render::view::ViewTarget; use bevy_render::{render_resource::*, RenderApp, RenderStage}; -use bevy_reflect::TypeUuid; +mod node; -use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; +pub use node::UpscalingNode; const UPSCALING_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 14589267395627146578); diff --git a/crates/bevy_derive/src/bevy_main.rs b/crates/bevy_derive/src/bevy_main.rs index 6ec1363097f25..fc5ea60bb0a1f 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 32875f0ae1814..7bd1249cbefaa 100644 --- a/crates/bevy_diagnostic/Cargo.toml +++ b/crates/bevy_diagnostic/Cargo.toml @@ -12,8 +12,19 @@ keywords = ["bevy"] [dependencies] # bevy bevy_app = { path = "../bevy_app", version = "0.9.0" } +bevy_core = { path = "../bevy_core", version = "0.9.0" } 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" } -bevy_core = { path = "../bevy_core", 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 076618bcde060..b2d127f115600 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 0000000000000..464fc06693da0 --- /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_dynamic_plugin/src/loader.rs b/crates/bevy_dynamic_plugin/src/loader.rs index 046bc1165be01..50f2bb34cb072 100644 --- a/crates/bevy_dynamic_plugin/src/loader.rs +++ b/crates/bevy_dynamic_plugin/src/loader.rs @@ -1,4 +1,5 @@ use libloading::{Library, Symbol}; +use std::ffi::OsStr; use thiserror::Error; use bevy_app::{App, CreatePlugin, Plugin}; @@ -20,8 +21,8 @@ pub enum DynamicPluginLoadError { /// The specified plugin must be linked against the exact same libbevy.so as this program. /// In addition the `_bevy_create_plugin` symbol must not be manually created, but instead created /// by deriving `DynamicPlugin` on a unit struct implementing [`Plugin`]. -pub unsafe fn dynamically_load_plugin( - path: &str, +pub unsafe fn dynamically_load_plugin>( + path: P, ) -> Result<(Library, Box), DynamicPluginLoadError> { let lib = Library::new(path).map_err(DynamicPluginLoadError::Library)?; let func: Symbol = lib @@ -35,11 +36,11 @@ pub trait DynamicPluginExt { /// # Safety /// /// Same as [`dynamically_load_plugin`]. - unsafe fn load_plugin(&mut self, path: &str) -> &mut Self; + unsafe fn load_plugin>(&mut self, path: P) -> &mut Self; } impl DynamicPluginExt for App { - unsafe fn load_plugin(&mut self, path: &str) -> &mut Self { + unsafe fn load_plugin>(&mut self, path: P) -> &mut Self { let (lib, plugin) = dynamically_load_plugin(path).unwrap(); std::mem::forget(lib); // Ensure that the library is not automatically unloaded plugin.build(self); diff --git a/crates/bevy_ecs/macros/src/fetch.rs b/crates/bevy_ecs/macros/src/fetch.rs index 4d04d1812b962..f4bd82b61a7f4 100644 --- a/crates/bevy_ecs/macros/src/fetch.rs +++ b/crates/bevy_ecs/macros/src/fetch.rs @@ -283,7 +283,7 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { unsafe fn fetch<'__w>( _fetch: &mut ::Fetch<'__w>, _entity: #path::entity::Entity, - _table_row: usize + _table_row: #path::storage::TableRow, ) -> ::Item<'__w> { Self::Item { #(#field_idents: <#field_types>::fetch(&mut _fetch.#field_idents, _entity, _table_row),)* @@ -296,7 +296,7 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { unsafe fn filter_fetch<'__w>( _fetch: &mut ::Fetch<'__w>, _entity: #path::entity::Entity, - _table_row: usize + _table_row: #path::storage::TableRow, ) -> bool { true #(&& <#field_types>::filter_fetch(&mut _fetch.#field_idents, _entity, _table_row))* } @@ -333,10 +333,9 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { let readonly_impl = if fetch_struct_attributes.is_mutable { let world_query_impl = impl_fetch(true); quote! { - #[doc(hidden)] - #[doc = "Automatically generated internal [`WorldQuery`] type for [`"] + #[doc = "Automatically generated [`WorldQuery`] type for a read-only variant of [`"] #[doc = stringify!(#struct_name)] - #[doc = "`], used for read-only access."] + #[doc = "`]."] #[automatically_derived] #visibility struct #read_only_struct_name #user_impl_generics #user_where_clauses { #( #field_idents: #read_only_field_types, )* diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index f67e58cf19be6..1a0d2f0c4770d 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 { @@ -170,7 +170,10 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { let struct_name = &ast.ident; TokenStream::from(quote! { - /// SAFETY: ComponentId is returned in field-definition-order. [from_components] and [get_components] use field-definition-order + // SAFETY: + // - ComponentId is returned in field-definition-order. [from_components] and [get_components] use field-definition-order + // - `Bundle::get_components` is exactly once for each member. Rely's on the Component -> Bundle implementation to properly pass + // the correct `StorageType` into the callback. unsafe impl #impl_generics #ecs_path::bundle::Bundle for #struct_name #ty_generics #where_clause { fn component_ids( components: &mut #ecs_path::component::Components, @@ -191,7 +194,11 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { } #[allow(unused_variables)] - fn get_components(self, func: &mut impl FnMut(#ecs_path::ptr::OwningPtr<'_>)) { + #[inline] + fn get_components( + self, + func: &mut impl FnMut(#ecs_path::component::StorageType, #ecs_path::ptr::OwningPtr<'_>) + ) { #(#field_get_components)* } } @@ -209,19 +216,29 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream { let mut tokens = TokenStream::new(); let max_params = 8; let params = get_idents(|i| format!("P{i}"), max_params); - let params_fetch = get_idents(|i| format!("PF{i}"), max_params); + let params_state = get_idents(|i| format!("PF{i}"), max_params); let metas = get_idents(|i| format!("m{i}"), max_params); let mut param_fn_muts = Vec::new(); 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! { - pub fn #fn_name<'a>(&'a mut self) -> <#param::Fetch as SystemParamFetch<'a, 'a>>::Item { + #[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 // ParamSets are guaranteed to not conflict with other SystemParams unsafe { - <#param::Fetch as SystemParamFetch<'a, 'a>>::get_param(&mut self.param_states.#index, &self.system_meta, self.world, self.change_tick) + <#param::State as SystemParamState>::get_param(&mut self.param_states.#index, &self.system_meta, self.world, self.change_tick) } } }); @@ -229,34 +246,36 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream { for param_count in 1..=max_params { let param = ¶ms[0..param_count]; - let param_fetch = ¶ms_fetch[0..param_count]; + let param_state = ¶ms_state[0..param_count]; let meta = &metas[0..param_count]; let param_fn_mut = ¶m_fn_muts[0..param_count]; tokens.extend(TokenStream::from(quote! { impl<'w, 's, #(#param: SystemParam,)*> SystemParam for ParamSet<'w, 's, (#(#param,)*)> { - type Fetch = ParamSetState<(#(#param::Fetch,)*)>; + type State = ParamSetState<(#(#param::State,)*)>; } - // SAFETY: All parameters are constrained to ReadOnlyFetch, so World is only read + // SAFETY: All parameters are constrained to ReadOnlyState, so World is only read - unsafe impl<#(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> ReadOnlySystemParamFetch for ParamSetState<(#(#param_fetch,)*)> - where #(#param_fetch: ReadOnlySystemParamFetch,)* + unsafe impl<'w, 's, #(#param,)*> ReadOnlySystemParam for ParamSet<'w, 's, (#(#param,)*)> + where #(#param: ReadOnlySystemParam,)* { } // SAFETY: Relevant parameter ComponentId and ArchetypeComponentId access is applied to SystemMeta. If any ParamState conflicts // with any prior access, a panic will occur. - unsafe impl<#(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> SystemParamState for ParamSetState<(#(#param_fetch,)*)> + unsafe impl<#(#param_state: SystemParamState,)*> SystemParamState for ParamSetState<(#(#param_state,)*)> { + type Item<'w, 's> = ParamSet<'w, 's, (#(<#param_state as SystemParamState>::Item::<'w, 's>,)*)>; + fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { #( // Pretend to add each param to the system alone, see if it conflicts let mut #meta = system_meta.clone(); #meta.component_access_set.clear(); #meta.archetype_component_access.clear(); - #param_fetch::init(world, &mut #meta); - let #param = #param_fetch::init(world, &mut system_meta.clone()); + #param_state::init(world, &mut #meta); + let #param = #param_state::init(world, &mut system_meta.clone()); )* #( system_meta @@ -276,24 +295,17 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream { )* } - fn apply(&mut self, world: &mut World) { - self.0.apply(world) + fn apply(&mut self, system_meta: &SystemMeta, world: &mut World) { + self.0.apply(system_meta, world) } - } - - - - impl<'w, 's, #(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> SystemParamFetch<'w, 's> for ParamSetState<(#(#param_fetch,)*)> - { - type Item = ParamSet<'w, 's, (#(<#param_fetch as SystemParamFetch<'w, 's>>::Item,)*)>; #[inline] - unsafe fn get_param( + unsafe fn get_param<'w, 's>( state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { ParamSet { param_states: &mut state.0, system_meta: system_meta.clone(), @@ -325,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| { ( @@ -356,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(); @@ -366,19 +379,44 @@ 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)); } } let generics = ast.generics; - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + // Emit an error if there's any unrecognized lifetime names. + for lt in generics.lifetimes() { + let ident = <.lifetime.ident; + let w = format_ident!("w"); + let s = format_ident!("s"); + if ident != &w && ident != &s { + return syn::Error::new_spanned( + lt, + r#"invalid lifetime name: expected `'w` or `'s` + 'w -- refers to data stored in the World. + 's -- refers to data stored in the SystemParam's state.'"#, + ) + .into_compile_error() + .into(); + } + } + + let (_impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 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(); @@ -387,37 +425,90 @@ 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(); + let read_only_where_clause = read_only_generics.make_where_clause(); + for field_type in &field_types { + read_only_where_clause + .predicates + .push(syn::parse_quote!(#field_type: #path::system::ReadOnlySystemParam)); + } + let struct_name = &ast.ident; - let fetch_struct_visibility = &ast.vis; + let state_struct_visibility = &ast.vis; TokenStream::from(quote! { // We define the FetchState struct in an anonymous scope to avoid polluting the user namespace. - // The struct can still be accessed via SystemParam::Fetch, e.g. EventReaderState can be accessed via - // as SystemParam>::Fetch + // The struct can still be accessed via SystemParam::State, e.g. EventReaderState can be accessed via + // as SystemParam>::State const _: () = { - impl #impl_generics #path::system::SystemParam for #struct_name #ty_generics #where_clause { - type Fetch = FetchState <(#(<#field_types as #path::system::SystemParam>::Fetch,)*), #punctuated_generic_idents>; + impl<'w, 's, #punctuated_generics> #path::system::SystemParam for #struct_name #ty_generics #where_clause { + type State = State<'w, 's, #punctuated_generic_idents>; } #[doc(hidden)] - #fetch_struct_visibility struct FetchState { + 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: TSystemParamState, - marker: std::marker::PhantomData(#punctuated_generic_idents)> + marker: std::marker::PhantomData(#punctuated_type_generic_idents)> } - unsafe impl #path::system::SystemParamState for FetchState #where_clause { + unsafe impl<'__w, '__s, #punctuated_generics> #path::system::SystemParamState for + State<'__w, '__s, #punctuated_generic_idents> + #where_clause { + type Item<'w, 's> = #struct_name #ty_generics; + fn init(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self { Self { - state: TSystemParamState::init(world, system_meta), + state: #path::system::SystemParamState::init(world, system_meta), marker: std::marker::PhantomData, } } @@ -426,28 +517,28 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { self.state.new_archetype(archetype, system_meta) } - fn apply(&mut self, world: &mut #path::world::World) { - self.state.apply(world) + fn apply(&mut self, system_meta: &#path::system::SystemMeta, world: &mut #path::world::World) { + self.state.apply(system_meta, world) } - } - impl #impl_generics #path::system::SystemParamFetch<'w, 's> for FetchState <(#(<#field_types as #path::system::SystemParam>::Fetch,)*), #punctuated_generic_idents> #where_clause { - type Item = #struct_name #ty_generics; - unsafe fn get_param( + unsafe fn get_param<'w, 's>( state: &'s mut Self, system_meta: &#path::system::SystemMeta, world: &'w #path::world::World, change_tick: u32, - ) -> Self::Item { + ) -> 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>::Fetch as #path::system::SystemParamFetch>::get_param(&mut state.state.#field_indices, system_meta, world, change_tick),)* + #(#fields: #field_locals,)* #(#ignored_fields: <#ignored_field_types>::default(),)* } } } - // Safety: The `ParamState` is `ReadOnlySystemParamFetch`, so this can only read from the `World` - unsafe impl #path::system::ReadOnlySystemParamFetch for FetchState #where_clause {} + // Safety: Each field is `ReadOnlySystemParam`, so this can only read from the `World` + unsafe impl<'w, 's, #punctuated_generics> #path::system::ReadOnlySystemParam for #struct_name #ty_generics #read_only_where_clause {} }; }) } diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 0fc9271a42c6c..ffd9fdc518783 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -1,11 +1,29 @@ //! Types for defining [`Archetype`]s, collections of entities that have the same set of //! components. +//! +//! An archetype uniquely describes a group of entities that share the same components: +//! a world only has one archetype for each unique combination of components, and all +//! entities that have those components and only those components belong to that +//! archetype. +//! +//! Archetypes are not to be confused with [`Table`]s. Each archetype stores its table +//! components in one table, and each archetype uniquely points to one table, but multiple +//! archetypes may store their table components in the same table. These archetypes +//! differ only by the [`SparseSet`] components. +//! +//! Like tables, archetypes can be created but are never cleaned up. Empty archetypes are +//! not removed, and persist until the world is dropped. +//! +//! Archetypes can be fetched from [`Archetypes`], which is accessible via [`World::archetypes`]. +//! +//! [`Table`]: crate::storage::Table +//! [`World::archetypes`]: crate::world::World::archetypes use crate::{ bundle::BundleId, component::{ComponentId, StorageType}, entity::{Entity, EntityLocation}, - storage::{SparseArray, SparseSet, SparseSetIndex, TableId}, + storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow}, }; use std::{ collections::HashMap, @@ -13,25 +31,62 @@ use std::{ ops::{Index, IndexMut}, }; +/// An opaque location within a [`Archetype`]. +/// +/// This can be used in conjunction with [`ArchetypeId`] to find the exact location +/// of an [`Entity`] within a [`World`]. An entity's archetype and index can be +/// retrieved via [`Entities::get`]. +/// +/// [`World`]: crate::world::World +/// [`Entities::get`]: crate::entity::Entities +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[repr(transparent)] +pub struct ArchetypeRow(u32); + +impl ArchetypeRow { + pub const INVALID: ArchetypeRow = ArchetypeRow(u32::MAX); + + /// Creates a `ArchetypeRow`. + pub const fn new(index: usize) -> Self { + Self(index as u32) + } + + /// Gets the index of the row. + #[inline] + pub const fn index(self) -> usize { + self.0 as usize + } +} + +/// An opaque unique ID for a single [`Archetype`] within a [`World`]. +/// +/// Archetype IDs are only valid for a given World, and are not globally unique. +/// Attempting to use an archetype ID on a world that it wasn't sourced from will +/// not return the archetype with the same components. The only exception to this is +/// [`EMPTY`] which is guarenteed to be identical for all Worlds. +/// +/// [`World`]: crate::world::World +/// [`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. pub const EMPTY: ArchetypeId = ArchetypeId(0); /// # 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 const fn new(index: usize) -> Self { - ArchetypeId(index) + pub(crate) const fn new(index: usize) -> Self { + ArchetypeId(index as u32) } #[inline] - pub fn index(self) -> usize { - self.0 + pub(crate) fn index(self) -> usize { + self.0 as usize } } @@ -41,9 +96,9 @@ pub(crate) enum ComponentStatus { Mutated, } -pub struct AddBundle { +pub(crate) struct AddBundle { pub archetype_id: ArchetypeId, - pub(crate) bundle_status: Vec, + pub bundle_status: Vec, } /// This trait is used to report the status of [`Bundle`](crate::bundle::Bundle) components @@ -71,7 +126,7 @@ pub(crate) struct SpawnBundleStatus; impl BundleComponentStatus for SpawnBundleStatus { #[inline] unsafe fn get_status(&self, _index: usize) -> ComponentStatus { - // Components added during a spawn_bundle call are always treated as added + // Components added during a spawn call are always treated as added ComponentStatus::Added } } @@ -98,11 +153,29 @@ pub struct Edges { } impl Edges { + /// Checks the cache for the target archetype when adding a bundle to the + /// source archetype. For more information, see [`EntityMut::insert`]. + /// + /// If this returns `None`, it means there has not been a transition from + /// the source archetype via the provided bundle. + /// + /// [`EntityMut::insert`]: crate::world::EntityMut::insert #[inline] - pub fn get_add_bundle(&self, bundle_id: BundleId) -> Option<&AddBundle> { + pub fn get_add_bundle(&self, bundle_id: BundleId) -> Option { + self.get_add_bundle_internal(bundle_id) + .map(|bundle| bundle.archetype_id) + } + + /// Internal version of `get_add_bundle` that fetches the full `AddBundle`. + #[inline] + pub(crate) fn get_add_bundle_internal(&self, bundle_id: BundleId) -> Option<&AddBundle> { self.add_bundle.get(bundle_id) } + /// Caches the target archetype when adding a bundle to the source archetype. + /// For more information, see [`EntityMut::insert`]. + /// + /// [`EntityMut::insert`]: crate::world::EntityMut::insert #[inline] pub(crate) fn insert_add_bundle( &mut self, @@ -119,11 +192,25 @@ impl Edges { ); } + /// Checks the cache for the target archetype when removing a bundle to the + /// source archetype. For more information, see [`EntityMut::remove`]. + /// + /// If this returns `None`, it means there has not been a transition from + /// the source archetype via the provided bundle. + /// + /// If this returns `Some(None)`, it means that the bundle cannot be removed + /// from the source archetype. + /// + /// [`EntityMut::remove`]: crate::world::EntityMut::remove #[inline] pub fn get_remove_bundle(&self, bundle_id: BundleId) -> Option> { self.remove_bundle.get(bundle_id).cloned() } + /// Caches the target archetype when removing a bundle to the source archetype. + /// For more information, see [`EntityMut::remove`]. + /// + /// [`EntityMut::remove`]: crate::world::EntityMut::remove #[inline] pub(crate) fn insert_remove_bundle( &mut self, @@ -133,6 +220,13 @@ impl Edges { self.remove_bundle.insert(bundle_id, archetype_id); } + /// Checks the cache for the target archetype when removing a bundle to the + /// source archetype. For more information, see [`EntityMut::remove_intersection`]. + /// + /// If this returns `None`, it means there has not been a transition from + /// the source archetype via the provided bundle. + /// + /// [`EntityMut::remove_intersection`]: crate::world::EntityMut::remove_intersection #[inline] pub fn get_remove_bundle_intersection( &self, @@ -141,6 +235,10 @@ impl Edges { self.remove_bundle_intersection.get(bundle_id).cloned() } + /// Caches the target archetype when removing a bundle to the source archetype. + /// For more information, see [`EntityMut::remove_intersection`]. + /// + /// [`EntityMut::remove_intersection`]: crate::world::EntityMut::remove_intersection #[inline] pub(crate) fn insert_remove_bundle_intersection( &mut self, @@ -152,57 +250,68 @@ impl Edges { } } +/// Metadata about an [`Entity`] in a [`Archetype`]. pub struct ArchetypeEntity { - pub(crate) entity: Entity, - pub(crate) table_row: usize, + entity: Entity, + table_row: TableRow, } impl ArchetypeEntity { - pub fn entity(&self) -> Entity { + /// The ID of the entity. + #[inline] + pub const fn entity(&self) -> Entity { self.entity } - pub fn table_row(&self) -> usize { + /// The row in the [`Table`] where the entity's components are stored. + /// + /// [`Table`]: crate::storage::Table + #[inline] + pub const fn table_row(&self) -> TableRow { self.table_row } } pub(crate) struct ArchetypeSwapRemoveResult { pub(crate) swapped_entity: Option, - pub(crate) table_row: usize, + pub(crate) table_row: TableRow, } -pub(crate) struct ArchetypeComponentInfo { - pub(crate) storage_type: StorageType, - pub(crate) archetype_component_id: ArchetypeComponentId, +/// Internal metadata for a [`Component`] within a given [`Archetype`]. +/// +/// [`Component`]: crate::component::Component +struct ArchetypeComponentInfo { + storage_type: StorageType, + archetype_component_id: ArchetypeComponentId, } +/// Metadata for a single archetype within a [`World`]. +/// +/// For more information, see the *[module level documentation]*. +/// +/// [`World`]: crate::world::World +/// [module level documentation]: crate::archetype pub struct Archetype { id: ArchetypeId, table_id: TableId, edges: Edges, entities: Vec, - table_components: Box<[ComponentId]>, - sparse_set_components: Box<[ComponentId]>, - components: SparseSet, + components: ImmutableSparseSet, } impl Archetype { - pub fn new( + pub(crate) fn new( id: ArchetypeId, table_id: TableId, - table_components: Box<[ComponentId]>, - sparse_set_components: Box<[ComponentId]>, - table_archetype_components: Vec, - sparse_set_archetype_components: Vec, + table_components: impl Iterator, + sparse_set_components: impl Iterator, ) -> Self { - let mut components = - SparseSet::with_capacity(table_components.len() + sparse_set_components.len()); - for (component_id, archetype_component_id) in - table_components.iter().zip(table_archetype_components) - { + let (min_table, _) = table_components.size_hint(); + let (min_sparse, _) = sparse_set_components.size_hint(); + let mut components = SparseSet::with_capacity(min_table + min_sparse); + for (component_id, archetype_component_id) in table_components { components.insert( - *component_id, + component_id, ArchetypeComponentInfo { storage_type: StorageType::Table, archetype_component_id, @@ -210,12 +319,9 @@ impl Archetype { ); } - for (component_id, archetype_component_id) in sparse_set_components - .iter() - .zip(sparse_set_archetype_components) - { + for (component_id, archetype_component_id) in sparse_set_components { components.insert( - *component_id, + component_id, ArchetypeComponentInfo { storage_type: StorageType::SparseSet, archetype_component_id, @@ -226,18 +332,20 @@ impl Archetype { id, table_id, entities: Vec::new(), - components, - table_components, - sparse_set_components, + components: components.into_immutable(), edges: Default::default(), } } + /// Fetches the ID for the archetype. #[inline] pub fn id(&self) -> ArchetypeId { self.id } + /// Fetches the archetype's [`Table`] ID. + /// + /// [`Table`]: crate::storage::Table #[inline] pub fn table_id(&self) -> TableId { self.table_id @@ -248,50 +356,99 @@ impl Archetype { &self.entities } + /// Gets an iterator of all of the components stored in [`Table`]s. + /// + /// All of the IDs are unique. + /// + /// [`Table`]: crate::storage::Table #[inline] - pub fn table_components(&self) -> &[ComponentId] { - &self.table_components + pub fn table_components(&self) -> impl Iterator + '_ { + self.components + .iter() + .filter(|(_, component)| component.storage_type == StorageType::Table) + .map(|(id, _)| *id) } + /// Gets an iterator of all of the components stored in [`ComponentSparseSet`]s. + /// + /// All of the IDs are unique. + /// + /// [`ComponentSparseSet`]: crate::storage::ComponentSparseSet #[inline] - pub fn sparse_set_components(&self) -> &[ComponentId] { - &self.sparse_set_components + pub fn sparse_set_components(&self) -> impl Iterator + '_ { + self.components + .iter() + .filter(|(_, component)| component.storage_type == StorageType::SparseSet) + .map(|(id, _)| *id) } + /// Gets an iterator of all of the components in the archetype. + /// + /// All of the IDs are unique. #[inline] pub fn components(&self) -> impl Iterator + '_ { self.components.indices() } + /// Fetches a immutable reference to the archetype's [`Edges`], a cache of + /// archetypal relationships. #[inline] pub fn edges(&self) -> &Edges { &self.edges } + /// Fetches a mutable reference to the archetype's [`Edges`], a cache of + /// archetypal relationships. #[inline] pub(crate) fn edges_mut(&mut self) -> &mut Edges { &mut self.edges } + /// Fetches the row in the [`Table`] where the components for the entity at `index` + /// is stored. + /// + /// 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::archetype_row`]: crate::entity::EntityLocation::archetype_row + /// [`Entities::get`]: crate::entity::Entities::get #[inline] - pub fn entity_table_row(&self, index: usize) -> usize { - self.entities[index].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 + /// in the corresponding table. + /// + /// # Panics + /// This function will panic if `index >= self.len()`. #[inline] - pub(crate) fn set_entity_table_row(&mut self, index: usize, table_row: usize) { - self.entities[index].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. + /// /// # Safety /// valid component values must be immediately written to the relevant storages /// `table_row` must be valid - pub(crate) unsafe fn allocate(&mut self, entity: Entity, table_row: usize) -> EntityLocation { + pub(crate) unsafe fn allocate( + &mut self, + 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, - index: self.entities.len() - 1, + archetype_row, + table_id: self.table_id, + table_row, } } @@ -301,34 +458,43 @@ impl Archetype { /// Removes the entity at `index` by swapping it out. Returns the table row the entity is stored /// in. - pub(crate) fn swap_remove(&mut self, index: usize) -> ArchetypeSwapRemoveResult { - let is_last = index == self.entities.len() - 1; - let entity = self.entities.swap_remove(index); + /// + /// # Panics + /// This function will panic if `index >= self.len()` + 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].entity) + Some(self.entities[row.index()].entity) }, table_row: entity.table_row, } } + /// Gets the total number of entities that belong to the archetype. #[inline] pub fn len(&self) -> usize { self.entities.len() } + /// Checks if the archetype has any entities. #[inline] pub fn is_empty(&self) -> bool { self.entities.is_empty() } + /// Checks if the archetype contains a specific component. This runs in `O(1)` time. #[inline] pub fn contains(&self, component_id: ComponentId) -> bool { self.components.contains(component_id) } + /// Gets the type of storage where a component in the archetype can be found. + /// Returns `None` if the component is not part of the archetype. + /// This runs in `O(1)` time. #[inline] pub fn get_storage_type(&self, component_id: ComponentId) -> Option { self.components @@ -336,6 +502,9 @@ impl Archetype { .map(|info| info.storage_type) } + /// Fetches the corresponding [`ArchetypeComponentId`] for a component in the archetype. + /// Returns `None` if the component is not part of the archetype. + /// This runs in `O(1)` time. #[inline] pub fn get_archetype_component_id( &self, @@ -346,46 +515,68 @@ impl Archetype { .map(|info| info.archetype_component_id) } + /// Clears all entities from the archetype. pub(crate) fn clear_entities(&mut self) { self.entities.clear(); } } -/// A generational id that changes every time the set of archetypes changes -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +/// An opaque generational id that changes every time the set of [`Archetypes`] changes. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] pub struct ArchetypeGeneration(usize); impl ArchetypeGeneration { #[inline] - pub const fn initial() -> Self { + pub(crate) const fn initial() -> Self { ArchetypeGeneration(0) } #[inline] - pub fn value(self) -> usize { + pub(crate) fn value(self) -> usize { self.0 } } #[derive(Hash, PartialEq, Eq)] -pub struct ArchetypeIdentity { +struct ArchetypeIdentity { table_components: Box<[ComponentId]>, sparse_set_components: Box<[ComponentId]>, } +/// An opaque unique joint ID for a [`Component`] in an [`Archetype`] within a [`World`]. +/// +/// A component may be present within multiple archetypes, but each component within +/// each archetype has its own unique `ArchetypeComponentId`. This is leveraged by the system +/// schedulers to opportunistically run multiple systems in parallel that would otherwise +/// conflict. For example, `Query<&mut A, With>` and `Query<&mut A, Without>` can run in +/// parallel as the matched `ArchetypeComponentId` sets for both queries are disjoint, even +/// though `&mut A` on both queries point to the same [`ComponentId`]. +/// +/// In SQL terms, these IDs are composite keys on a [many-to-many relationship] between archetypes +/// and components. Each component type will have only one [`ComponentId`], but may have many +/// [`ArchetypeComponentId`]s, one for every archetype the component is present in. Likewise, each +/// archetype will have only one [`ArchetypeId`] but may have many [`ArchetypeComponentId`]s, one +/// for each component that belongs to the archetype. +/// +/// Every [`Resource`] is also assigned one of these IDs. As resources do not belong to any +/// particular archetype, a resource's ID uniquely identifies it. +/// +/// These IDs are only valid within a given World, and are not globally unique. +/// Attempting to use an ID on a world that it wasn't sourced from will +/// not point to the same archetype nor the same component. +/// +/// [`Component`]: crate::component::Component +/// [`World`]: crate::world::World +/// [`Resource`]: crate::system::Resource +/// [many-to-many relationship]: https://en.wikipedia.org/wiki/Many-to-many_(data_model) #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct ArchetypeComponentId(usize); impl ArchetypeComponentId { #[inline] - pub const fn new(index: usize) -> Self { + pub(crate) const fn new(index: usize) -> Self { Self(index) } - - #[inline] - pub fn index(self) -> usize { - self.0 - } } impl SparseSetIndex for ArchetypeComponentId { @@ -399,14 +590,20 @@ impl SparseSetIndex for ArchetypeComponentId { } } +/// The backing store of all [`Archetype`]s within a [`World`]. +/// +/// For more information, see the *[module level documentation]*. +/// +/// [`World`]: crate::world::World +/// [*module level documentation]: crate::archetype pub struct Archetypes { pub(crate) archetypes: Vec, pub(crate) archetype_component_count: usize, archetype_ids: HashMap, } -impl Default for Archetypes { - fn default() -> Self { +impl Archetypes { + pub(crate) fn new() -> Self { let mut archetypes = Archetypes { archetypes: Vec::new(), archetype_ids: Default::default(), @@ -415,25 +612,29 @@ impl Default for Archetypes { archetypes.get_id_or_insert(TableId::empty(), Vec::new(), Vec::new()); archetypes } -} -impl Archetypes { #[inline] pub fn generation(&self) -> ArchetypeGeneration { ArchetypeGeneration(self.archetypes.len()) } + /// Fetches the total number of [`Archetype`]s within the world. #[inline] + #[allow(clippy::len_without_is_empty)] // the internal vec is never empty. pub fn len(&self) -> usize { self.archetypes.len() } + /// Fetches an immutable reference to the archetype without any compoennts. + /// + /// Shorthand for `archetypes.get(ArchetypeId::EMPTY).unwrap()` #[inline] pub fn empty(&self) -> &Archetype { // SAFETY: empty archetype always exists unsafe { self.archetypes.get_unchecked(ArchetypeId::EMPTY.index()) } } + /// Fetches an mutable reference to the archetype without any compoennts. #[inline] pub(crate) fn empty_mut(&mut self) -> &mut Archetype { // SAFETY: empty archetype always exists @@ -443,11 +644,8 @@ impl Archetypes { } } - #[inline] - pub fn is_empty(&self) -> bool { - self.archetypes.is_empty() - } - + /// Fetches an immutable reference to an [`Archetype`] using its + /// ID. Returns `None` if no corresponding archetype exists. #[inline] pub fn get(&self, id: ArchetypeId) -> Option<&Archetype> { self.archetypes.get(id.index()) @@ -468,6 +666,7 @@ impl Archetypes { } } + /// Returns a read-only iterator over all archetypes. #[inline] pub fn iter(&self) -> impl Iterator { self.archetypes.iter() @@ -484,38 +683,33 @@ impl Archetypes { table_components: Vec, sparse_set_components: Vec, ) -> ArchetypeId { - let table_components = table_components.into_boxed_slice(); - let sparse_set_components = sparse_set_components.into_boxed_slice(); let archetype_identity = ArchetypeIdentity { - sparse_set_components: sparse_set_components.clone(), - table_components: table_components.clone(), + sparse_set_components: sparse_set_components.clone().into_boxed_slice(), + table_components: table_components.clone().into_boxed_slice(), }; let archetypes = &mut self.archetypes; let archetype_component_count = &mut self.archetype_component_count; - let mut next_archetype_component_id = move || { - let id = ArchetypeComponentId(*archetype_component_count); - *archetype_component_count += 1; - id - }; *self .archetype_ids .entry(archetype_identity) .or_insert_with(move || { - let id = ArchetypeId(archetypes.len()); - let table_archetype_components = (0..table_components.len()) - .map(|_| next_archetype_component_id()) - .collect(); - let sparse_set_archetype_components = (0..sparse_set_components.len()) - .map(|_| next_archetype_component_id()) - .collect(); + let id = ArchetypeId::new(archetypes.len()); + let table_start = *archetype_component_count; + *archetype_component_count += table_components.len(); + let table_archetype_components = + (table_start..*archetype_component_count).map(ArchetypeComponentId); + let sparse_start = *archetype_component_count; + *archetype_component_count += sparse_set_components.len(); + let sparse_set_archetype_components = + (sparse_start..*archetype_component_count).map(ArchetypeComponentId); archetypes.push(Archetype::new( id, table_id, - table_components, - sparse_set_components, - table_archetype_components, - sparse_set_archetype_components, + table_components.into_iter().zip(table_archetype_components), + sparse_set_components + .into_iter() + .zip(sparse_set_archetype_components), )); id }) @@ -526,6 +720,7 @@ impl Archetypes { self.archetype_component_count } + /// Clears all entities from all archetypes. pub(crate) fn clear_entities(&mut self) { for archetype in &mut self.archetypes { archetype.clear_entities(); diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 8dd5b5225d5c3..259de1abed446 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -9,9 +9,9 @@ use crate::{ Archetype, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus, SpawnBundleStatus, }, - component::{Component, ComponentId, ComponentTicks, Components, StorageType}, + component::{Component, ComponentId, ComponentStorage, Components, StorageType, Tick}, entity::{Entities, Entity, EntityLocation}, - storage::{SparseSetIndex, SparseSets, Storages, Table}, + storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, }; use bevy_ecs_macros::all_tuples; use bevy_ptr::OwningPtr; @@ -50,7 +50,7 @@ use std::{any::TypeId, collections::HashMap}; /// component, but specifying different render graphs to use. /// If the bundles were both added to the same entity, only one of these two bundles would work. /// -/// For this reason, There is intentionally no [`Query`] to match whether an entity +/// For this reason, there is intentionally no [`Query`] to match whether an entity /// contains the components of a bundle. /// Queries should instead only select the components they logically operate on. /// @@ -130,7 +130,6 @@ use std::{any::TypeId, collections::HashMap}; /// That is, there is no safe way to implement this trait, and you must not do so. /// If you want a type to implement [`Bundle`], you must use [`derive@Bundle`](derive@Bundle). /// -/// /// [`Query`]: crate::system::Query // Some safety points: // - [`Bundle::component_ids`] must return the [`ComponentId`] for each component type in the @@ -159,15 +158,19 @@ pub unsafe trait Bundle: Send + Sync + 'static { F: for<'a> FnMut(&'a mut T) -> OwningPtr<'a>, Self: Sized; + // SAFETY: + // The `StorageType` argument passed into [`Bundle::get_components`] must be correct for the + // component being fetched. + // /// Calls `func` on each value, in the order of this bundle's [`Component`]s. This passes /// ownership of the component values to `func`. #[doc(hidden)] - fn get_components(self, func: &mut impl FnMut(OwningPtr<'_>)); + fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)); } // SAFETY: // - `Bundle::component_ids` calls `ids` for C's component id (and nothing else) -// - `Bundle::get_components` is called exactly once for C. +// - `Bundle::get_components` is called exactly once for C and passes the component's storage type based on it's associated constant. // - `Bundle::from_components` calls `func` exactly once for C, which is the exact value returned by `Bundle::component_ids`. unsafe impl Bundle for C { fn component_ids( @@ -188,8 +191,9 @@ unsafe impl Bundle for C { func(ctx).read() } - fn get_components(self, func: &mut impl FnMut(OwningPtr<'_>)) { - OwningPtr::make(self, func); + #[inline] + fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) { + OwningPtr::make(self, |ptr| func(C::Storage::STORAGE_TYPE, ptr)); } } @@ -199,6 +203,8 @@ macro_rules! tuple_impl { // - `Bundle::component_ids` calls `ids` for each component type in the // bundle, in the exact order that `Bundle::get_components` is called. // - `Bundle::from_components` calls `func` exactly once for each `ComponentId` returned by `Bundle::component_ids`. + // - `Bundle::get_components` is called exactly once for each member. Relies on the above implementation to pass the correct + // `StorageType` into the callback. unsafe impl<$($name: Bundle),*> Bundle for ($($name,)*) { #[allow(unused_variables)] fn component_ids(components: &mut Components, storages: &mut Storages, ids: &mut impl FnMut(ComponentId)){ @@ -217,7 +223,8 @@ macro_rules! tuple_impl { } #[allow(unused_variables, unused_mut)] - fn get_components(self, func: &mut impl FnMut(OwningPtr<'_>)) { + #[inline(always)] + fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) { #[allow(non_snake_case)] let ($(mut $name,)*) = self; $( @@ -254,7 +261,6 @@ impl SparseSetIndex for BundleId { pub struct BundleInfo { pub(crate) id: BundleId, pub(crate) component_ids: Vec, - pub(crate) storage_types: Vec, } impl BundleInfo { @@ -268,11 +274,6 @@ impl BundleInfo { &self.component_ids } - #[inline] - pub fn storage_types(&self) -> &[StorageType] { - &self.storage_types - } - pub(crate) fn get_bundle_inserter<'a, 'b>( &'b self, entities: &'a mut Entities, @@ -379,26 +380,22 @@ impl BundleInfo { sparse_sets: &mut SparseSets, bundle_component_status: &S, entity: Entity, - table_row: usize, + table_row: TableRow, change_tick: u32, bundle: T, ) { // NOTE: get_components calls this closure on each component in "bundle order". // bundle_info.component_ids are also in "bundle order" let mut bundle_component = 0; - bundle.get_components(&mut |component_ptr| { + bundle.get_components(&mut |storage_type, component_ptr| { let component_id = *self.component_ids.get_unchecked(bundle_component); - match self.storage_types[bundle_component] { + match storage_type { StorageType::Table => { let column = table.get_column_mut(component_id).unwrap(); // SAFETY: bundle_component is a valid index for this bundle match bundle_component_status.get_status(bundle_component) { ComponentStatus::Added => { - column.initialize( - table_row, - component_ptr, - ComponentTicks::new(change_tick), - ); + column.initialize(table_row, component_ptr, Tick::new(change_tick)); } ComponentStatus::Mutated => { column.replace(table_row, component_ptr, change_tick); @@ -406,8 +403,11 @@ impl BundleInfo { } } StorageType::SparseSet => { - let sparse_set = sparse_sets.get_mut(component_id).unwrap(); - sparse_set.insert(entity, component_ptr, change_tick); + sparse_sets.get_mut(component_id).unwrap().insert( + entity, + component_ptr, + change_tick, + ); } } bundle_component += 1; @@ -424,8 +424,8 @@ impl BundleInfo { components: &mut Components, archetype_id: ArchetypeId, ) -> ArchetypeId { - if let Some(add_bundle) = archetypes[archetype_id].edges().get_add_bundle(self.id) { - return add_bundle.archetype_id; + if let Some(add_bundle_id) = archetypes[archetype_id].edges().get_add_bundle(self.id) { + return add_bundle_id; } let mut new_table_components = Vec::new(); let mut new_sparse_set_components = Vec::new(); @@ -461,7 +461,7 @@ impl BundleInfo { table_components = if new_table_components.is_empty() { // if there are no new table components, we can keep using this table table_id = current_archetype.table_id(); - current_archetype.table_components().to_vec() + current_archetype.table_components().collect() } else { new_table_components.extend(current_archetype.table_components()); // sort to ignore order while hashing @@ -477,7 +477,7 @@ impl BundleInfo { }; sparse_set_components = if new_sparse_set_components.is_empty() { - current_archetype.sparse_set_components().to_vec() + current_archetype.sparse_set_components().collect() } else { new_sparse_set_components.extend(current_archetype.sparse_set_components()); // sort to ignore order while hashing @@ -522,51 +522,47 @@ pub(crate) enum InsertBundleResult<'a> { impl<'a, 'b> BundleInserter<'a, 'b> { /// # Safety - /// `entity` must currently exist in the source archetype for this inserter. `archetype_index` + /// `entity` must currently exist in the source archetype for this inserter. `archetype_row` /// must be `entity`'s location in the archetype. `T` must match this [`BundleInfo`]'s type #[inline] pub unsafe fn insert( &mut self, entity: Entity, - archetype_index: usize, + location: EntityLocation, bundle: T, ) -> EntityLocation { - let location = EntityLocation { - index: archetype_index, - 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) let add_bundle = self .archetype .edges() - .get_add_bundle(self.bundle_info.id) + .get_add_bundle_internal(self.bundle_info.id) .unwrap(); self.bundle_info.write_components( self.table, self.sparse_sets, add_bundle, entity, - self.archetype.entity_table_row(archetype_index), + location.table_row, self.change_tick, bundle, ); location } InsertBundleResult::NewArchetypeSameTable { new_archetype } => { - let result = self.archetype.swap_remove(location.index); + let result = self.archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { - self.entities.meta[swapped_entity.index as usize].location = location; + self.entities.set(swapped_entity.index(), location); } let new_location = new_archetype.allocate(entity, result.table_row); - self.entities.meta[entity.index as usize].location = new_location; + self.entities.set(entity.index(), new_location); // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) let add_bundle = self .archetype .edges() - .get_add_bundle(self.bundle_info.id) + .get_add_bundle_internal(self.bundle_info.id) .unwrap(); self.bundle_info.write_components( self.table, @@ -583,9 +579,9 @@ impl<'a, 'b> BundleInserter<'a, 'b> { new_archetype, new_table, } => { - let result = self.archetype.swap_remove(location.index); + let result = self.archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = result.swapped_entity { - self.entities.meta[swapped_entity.index as usize].location = location; + self.entities.set(swapped_entity.index(), location); } // PERF: store "non bundle" components in edge, then just move those to avoid // redundant copies @@ -593,7 +589,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> { .table .move_to_superset_unchecked(result.table_row, new_table); let new_location = new_archetype.allocate(entity, move_result.new_row); - self.entities.meta[entity.index as usize].location = new_location; + self.entities.set(entity.index(), new_location); // if an entity was moved into this entity's table spot, update its table row if let Some(swapped_entity) = move_result.swapped_entity { @@ -611,14 +607,14 @@ impl<'a, 'b> BundleInserter<'a, 'b> { }; swapped_archetype - .set_entity_table_row(swapped_location.index, result.table_row); + .set_entity_table_row(swapped_location.archetype_row, result.table_row); } // PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty) let add_bundle = self .archetype .edges() - .get_add_bundle(self.bundle_info.id) + .get_add_bundle_internal(self.bundle_info.id) .unwrap(); self.bundle_info.write_components( new_table, @@ -668,7 +664,7 @@ impl<'a, 'b> BundleSpawner<'a, 'b> { self.change_tick, bundle, ); - self.entities.meta[entity.index as usize].location = location; + self.entities.set(entity.index(), location); location } @@ -711,10 +707,9 @@ impl Bundles { let mut component_ids = Vec::new(); T::component_ids(components, storages, &mut |id| component_ids.push(id)); let id = BundleId(bundle_infos.len()); - // SAFETY: T::component_id ensures info was created - let bundle_info = unsafe { - initialize_bundle(std::any::type_name::(), component_ids, id, components) - }; + let bundle_info = + // SAFETY: T::component_id ensures info was created + unsafe { initialize_bundle(std::any::type_name::(), component_ids, id) }; bundle_infos.push(bundle_info); id }); @@ -730,16 +725,7 @@ unsafe fn initialize_bundle( bundle_type_name: &'static str, component_ids: Vec, id: BundleId, - components: &mut Components, ) -> BundleInfo { - let mut storage_types = Vec::new(); - - for &component_id in &component_ids { - // SAFETY: component_id exists and is therefore valid - let component_info = components.get_info_unchecked(component_id); - storage_types.push(component_info.storage_type()); - } - let mut deduped = component_ids.clone(); deduped.sort(); deduped.dedup(); @@ -749,9 +735,5 @@ unsafe fn initialize_bundle( bundle_type_name ); - BundleInfo { - id, - component_ids, - storage_types, - } + BundleInfo { id, component_ids } } diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index b8d1f7c196d35..d029d9dad9bf9 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -1,6 +1,11 @@ //! Types that detect when their internal data mutate. -use crate::{component::ComponentTicks, ptr::PtrMut, system::Resource}; +use crate::{ + component::{Tick, TickCells}, + ptr::PtrMut, + system::Resource, +}; +use bevy_ptr::{Ptr, UnsafeCellDeref}; use std::ops::{Deref, DerefMut}; /// The (arbitrarily chosen) minimum number of world tick increments between `check_tick` scans. @@ -26,6 +31,13 @@ pub const MAX_CHANGE_AGE: u32 = u32::MAX - (2 * CHECK_TICK_THRESHOLD - 1); /// Normally change detecting is triggered by either [`DerefMut`] or [`AsMut`], however /// it can be manually triggered via [`DetectChanges::set_changed`]. /// +/// To ensure that changes are only triggered when the value actually differs, +/// check if the value would change before assignment, such as by checking that `new != old`. +/// You must be *sure* that you are not mutably derefencing in this process. +/// +/// [`set_if_neq`](DetectChanges::set_if_neq) is a helper +/// method for this common functionality. +/// /// ``` /// use bevy_ecs::prelude::*; /// @@ -85,6 +97,17 @@ pub trait DetectChanges { /// However, it can be an essential escape hatch when, for example, /// you are trying to synchronize representations using change detection and need to avoid infinite recursion. fn bypass_change_detection(&mut self) -> &mut Self::Inner; + + /// Sets `self` to `value`, if and only if `*self != *value` + /// + /// `T` is the type stored within the smart pointer (e.g. [`Mut`] or [`ResMut`]). + /// + /// This is useful to ensure change detection is only triggered when the underlying value + /// changes, instead of every time [`DerefMut`] is used. + fn set_if_neq(&mut self, value: Target) + where + Self: Deref + DerefMut, + Target: PartialEq; } macro_rules! change_detection_impl { @@ -95,21 +118,21 @@ macro_rules! change_detection_impl { #[inline] fn is_added(&self) -> bool { self.ticks - .component_ticks - .is_added(self.ticks.last_change_tick, self.ticks.change_tick) + .added + .is_older_than(self.ticks.last_change_tick, self.ticks.change_tick) } #[inline] fn is_changed(&self) -> bool { self.ticks - .component_ticks - .is_changed(self.ticks.last_change_tick, self.ticks.change_tick) + .changed + .is_older_than(self.ticks.last_change_tick, self.ticks.change_tick) } #[inline] fn set_changed(&mut self) { self.ticks - .component_ticks + .changed .set_changed(self.ticks.change_tick); } @@ -127,6 +150,19 @@ macro_rules! change_detection_impl { fn bypass_change_detection(&mut self) -> &mut Self::Inner { self.value } + + #[inline] + fn set_if_neq(&mut self, value: Target) + where + Self: Deref + DerefMut, + Target: PartialEq, + { + // This dereference is immutable, so does not trigger change detection + if *::deref(self) != value { + // `DerefMut` usage triggers change detection + *::deref_mut(self) = value; + } + } } impl<$($generics),*: ?Sized $(+ $traits)?> Deref for $name<$($generics),*> { @@ -224,11 +260,30 @@ macro_rules! impl_debug { } pub(crate) struct Ticks<'a> { - pub(crate) component_ticks: &'a mut ComponentTicks, + pub(crate) added: &'a mut Tick, + pub(crate) changed: &'a mut Tick, pub(crate) last_change_tick: u32, pub(crate) change_tick: u32, } +impl<'a> Ticks<'a> { + /// # Safety + /// This should never alias the underlying ticks. All access must be unique. + #[inline] + pub(crate) unsafe fn from_tick_cells( + cells: TickCells<'a>, + last_change_tick: u32, + change_tick: u32, + ) -> Self { + Self { + added: cells.added.deref_mut(), + changed: cells.changed.deref_mut(), + last_change_tick, + change_tick, + } + } +} + /// Unique mutable borrow of a [`Resource`]. /// /// See the [`Resource`] documentation for usage. @@ -366,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> { @@ -381,22 +452,20 @@ impl<'a> DetectChanges for MutUntyped<'a> { #[inline] fn is_added(&self) -> bool { self.ticks - .component_ticks - .is_added(self.ticks.last_change_tick, self.ticks.change_tick) + .added + .is_older_than(self.ticks.last_change_tick, self.ticks.change_tick) } #[inline] fn is_changed(&self) -> bool { self.ticks - .component_ticks - .is_changed(self.ticks.last_change_tick, self.ticks.change_tick) + .changed + .is_older_than(self.ticks.last_change_tick, self.ticks.change_tick) } #[inline] fn set_changed(&mut self) { - self.ticks - .component_ticks - .set_changed(self.ticks.change_tick); + self.ticks.changed.set_changed(self.ticks.change_tick); } #[inline] @@ -413,6 +482,19 @@ impl<'a> DetectChanges for MutUntyped<'a> { fn bypass_change_detection(&mut self) -> &mut Self::Inner { &mut self.value } + + #[inline] + fn set_if_neq(&mut self, value: Target) + where + Self: Deref + DerefMut, + Target: PartialEq, + { + // This dereference is immutable, so does not trigger change detection + if *::deref(self) != value { + // `DerefMut` usage triggers change detection + *::deref_mut(self) = value; + } + } } impl std::fmt::Debug for MutUntyped<'_> { @@ -429,21 +511,24 @@ mod tests { use crate::{ self as bevy_ecs, - change_detection::{ - ComponentTicks, Mut, NonSendMut, ResMut, Ticks, CHECK_TICK_THRESHOLD, MAX_CHANGE_AGE, - }, - component::Component, + change_detection::{Mut, NonSendMut, ResMut, Ticks, CHECK_TICK_THRESHOLD, MAX_CHANGE_AGE}, + component::{Component, ComponentTicks, Tick}, query::ChangeTrackers, system::{IntoSystem, Query, System}, world::World, }; - #[derive(Component)] + use super::DetectChanges; + + #[derive(Component, PartialEq)] struct C; #[derive(Resource)] struct R; + #[derive(Resource, PartialEq)] + struct R2(u8); + #[test] fn change_expiration() { fn change_detected(query: Query>) -> bool { @@ -514,8 +599,8 @@ mod tests { let mut query = world.query::>(); for tracker in query.iter(&world) { - let ticks_since_insert = change_tick.wrapping_sub(tracker.component_ticks.added); - let ticks_since_change = change_tick.wrapping_sub(tracker.component_ticks.changed); + let ticks_since_insert = change_tick.wrapping_sub(tracker.component_ticks.added.tick); + let ticks_since_change = change_tick.wrapping_sub(tracker.component_ticks.changed.tick); assert!(ticks_since_insert > MAX_CHANGE_AGE); assert!(ticks_since_change > MAX_CHANGE_AGE); } @@ -524,8 +609,8 @@ mod tests { world.check_change_ticks(); for tracker in query.iter(&world) { - let ticks_since_insert = change_tick.wrapping_sub(tracker.component_ticks.added); - let ticks_since_change = change_tick.wrapping_sub(tracker.component_ticks.changed); + let ticks_since_insert = change_tick.wrapping_sub(tracker.component_ticks.added.tick); + let ticks_since_change = change_tick.wrapping_sub(tracker.component_ticks.changed.tick); assert!(ticks_since_insert == MAX_CHANGE_AGE); assert!(ticks_since_change == MAX_CHANGE_AGE); } @@ -534,11 +619,12 @@ mod tests { #[test] fn mut_from_res_mut() { let mut component_ticks = ComponentTicks { - added: 1, - changed: 2, + added: Tick::new(1), + changed: Tick::new(2), }; let ticks = Ticks { - component_ticks: &mut component_ticks, + added: &mut component_ticks.added, + changed: &mut component_ticks.changed, last_change_tick: 3, change_tick: 4, }; @@ -549,8 +635,8 @@ mod tests { }; let into_mut: Mut = res_mut.into(); - assert_eq!(1, into_mut.ticks.component_ticks.added); - assert_eq!(2, into_mut.ticks.component_ticks.changed); + assert_eq!(1, into_mut.ticks.added.tick); + assert_eq!(2, into_mut.ticks.changed.tick); assert_eq!(3, into_mut.ticks.last_change_tick); assert_eq!(4, into_mut.ticks.change_tick); } @@ -558,11 +644,12 @@ mod tests { #[test] fn mut_from_non_send_mut() { let mut component_ticks = ComponentTicks { - added: 1, - changed: 2, + added: Tick::new(1), + changed: Tick::new(2), }; let ticks = Ticks { - component_ticks: &mut component_ticks, + added: &mut component_ticks.added, + changed: &mut component_ticks.changed, last_change_tick: 3, change_tick: 4, }; @@ -573,8 +660,8 @@ mod tests { }; let into_mut: Mut = non_send_mut.into(); - assert_eq!(1, into_mut.ticks.component_ticks.added); - assert_eq!(2, into_mut.ticks.component_ticks.changed); + assert_eq!(1, into_mut.ticks.added.tick); + assert_eq!(2, into_mut.ticks.changed.tick); assert_eq!(3, into_mut.ticks.last_change_tick); assert_eq!(4, into_mut.ticks.change_tick); } @@ -584,13 +671,14 @@ mod tests { use super::*; struct Outer(i64); + let (last_change_tick, change_tick) = (2, 3); let mut component_ticks = ComponentTicks { - added: 1, - changed: 2, + added: Tick::new(1), + changed: Tick::new(2), }; - let (last_change_tick, change_tick) = (2, 3); let ticks = Ticks { - component_ticks: &mut component_ticks, + added: &mut component_ticks.added, + changed: &mut component_ticks.changed, last_change_tick, change_tick, }; @@ -612,4 +700,30 @@ mod tests { // Modifying one field of a component should flag a change for the entire component. assert!(component_ticks.is_changed(last_change_tick, change_tick)); } + + #[test] + fn set_if_neq() { + let mut world = World::new(); + + world.insert_resource(R2(0)); + // Resources are Changed when first added + world.increment_change_tick(); + // This is required to update world::last_change_tick + world.clear_trackers(); + + let mut r = world.resource_mut::(); + assert!(!r.is_changed(), "Resource must begin unchanged."); + + r.set_if_neq(R2(0)); + assert!( + !r.is_changed(), + "Resource must not be changed after setting to the same value." + ); + + r.set_if_neq(R2(3)); + assert!( + r.is_changed(), + "Resource must be changed after setting to a different value." + ); + } } diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index c59a1e2028737..39f89b95ffaaf 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -6,7 +6,8 @@ use crate::{ system::Resource, }; pub use bevy_ecs_macros::Component; -use bevy_ptr::OwningPtr; +use bevy_ptr::{OwningPtr, UnsafeCellDeref}; +use std::cell::UnsafeCell; use std::{ alloc::Layout, any::{Any, TypeId}, @@ -109,6 +110,38 @@ use std::{ /// /// [orphan rule]: https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type /// [newtype pattern]: https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types +/// +/// # `!Sync` Components +/// A `!Sync` type cannot implement `Component`. However, it is possible to wrap a `Send` but not `Sync` +/// type in [`SyncCell`] or the currently unstable [`Exclusive`] to make it `Sync`. This forces only +/// having mutable access (`&mut T` only, never `&T`), but makes it safe to reference across multiple +/// threads. +/// +/// This will fail to compile since `RefCell` is `!Sync`. +/// ```compile_fail +/// # use std::cell::RefCell; +/// # use bevy_ecs::component::Component; +/// #[derive(Component)] +/// struct NotSync { +/// counter: RefCell, +/// } +/// ``` +/// +/// This will compile since the `RefCell` is wrapped with `SyncCell`. +/// ``` +/// # use std::cell::RefCell; +/// # use bevy_ecs::component::Component; +/// use bevy_utils::synccell::SyncCell; +/// +/// // This will compile. +/// #[derive(Component)] +/// struct ActuallySync { +/// counter: SyncCell>, +/// } +/// ``` +/// +/// [`SyncCell`]: bevy_utils::synccell::SyncCell +/// [`Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html pub trait Component: Send + Sync + 'static { type Storage: ComponentStorage; } @@ -517,23 +550,26 @@ impl Components { } } -/// Records when a component was added and when it was last mutably dereferenced (or added). +/// Used to track changes in state between system runs, e.g. components being added or accessed mutably. #[derive(Copy, Clone, Debug)] -pub struct ComponentTicks { - pub(crate) added: u32, - pub(crate) changed: u32, +pub struct Tick { + pub(crate) tick: u32, } -impl ComponentTicks { +impl Tick { + pub const fn new(tick: u32) -> Self { + Self { tick } + } + #[inline] - /// Returns `true` if the component was added after the system last ran. - pub fn is_added(&self, last_change_tick: u32, change_tick: u32) -> bool { + /// Returns `true` if the tick is older than the system last's run. + pub fn is_older_than(&self, last_change_tick: u32, change_tick: u32) -> bool { // This works even with wraparound because the world tick (`change_tick`) is always "newer" than - // `last_change_tick` and `self.added`, and we scan periodically to clamp `ComponentTicks` values + // `last_change_tick` and `self.tick`, and we scan periodically to clamp `ComponentTicks` values // so they never get older than `u32::MAX` (the difference would overflow). // // The clamp here ensures determinism (since scans could differ between app runs). - let ticks_since_insert = change_tick.wrapping_sub(self.added).min(MAX_CHANGE_AGE); + let ticks_since_insert = change_tick.wrapping_sub(self.tick).min(MAX_CHANGE_AGE); let ticks_since_system = change_tick .wrapping_sub(last_change_tick) .min(MAX_CHANGE_AGE); @@ -541,34 +577,81 @@ impl ComponentTicks { ticks_since_system > ticks_since_insert } + pub(crate) fn check_tick(&mut self, change_tick: u32) { + let age = change_tick.wrapping_sub(self.tick); + // This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true + // so long as this check always runs before that can happen. + if age > MAX_CHANGE_AGE { + self.tick = change_tick.wrapping_sub(MAX_CHANGE_AGE); + } + } + + /// Manually sets the change tick. + /// + /// This is normally done automatically via the [`DerefMut`](std::ops::DerefMut) implementation + /// on [`Mut`](crate::change_detection::Mut), [`ResMut`](crate::change_detection::ResMut), etc. + /// However, components and resources that make use of interior mutability might require manual updates. + /// + /// # Example + /// ```rust,no_run + /// # use bevy_ecs::{world::World, component::ComponentTicks}; + /// let world: World = unimplemented!(); + /// let component_ticks: ComponentTicks = unimplemented!(); + /// + /// component_ticks.set_changed(world.read_change_tick()); + /// ``` + #[inline] + pub fn set_changed(&mut self, change_tick: u32) { + self.tick = change_tick; + } +} + +/// Wrapper around [`Tick`]s for a single component +#[derive(Copy, Clone, Debug)] +pub struct TickCells<'a> { + pub added: &'a UnsafeCell, + pub changed: &'a UnsafeCell, +} + +impl<'a> TickCells<'a> { + /// # Safety + /// All cells contained within must uphold the safety invariants of [`UnsafeCellDeref::read`]. + #[inline] + pub(crate) unsafe fn read(&self) -> ComponentTicks { + ComponentTicks { + added: self.added.read(), + changed: self.changed.read(), + } + } +} + +/// Records when a component was added and when it was last mutably dereferenced (or added). +#[derive(Copy, Clone, Debug)] +pub struct ComponentTicks { + pub(crate) added: Tick, + pub(crate) changed: Tick, +} + +impl ComponentTicks { + #[inline] + /// Returns `true` if the component was added after the system last ran. + pub fn is_added(&self, last_change_tick: u32, change_tick: u32) -> bool { + self.added.is_older_than(last_change_tick, change_tick) + } + #[inline] /// Returns `true` if the component was added or mutably dereferenced after the system last ran. pub fn is_changed(&self, last_change_tick: u32, change_tick: u32) -> bool { - // This works even with wraparound because the world tick (`change_tick`) is always "newer" than - // `last_change_tick` and `self.changed`, and we scan periodically to clamp `ComponentTicks` values - // so they never get older than `u32::MAX` (the difference would overflow). - // - // The clamp here ensures determinism (since scans could differ between app runs). - let ticks_since_change = change_tick.wrapping_sub(self.changed).min(MAX_CHANGE_AGE); - let ticks_since_system = change_tick - .wrapping_sub(last_change_tick) - .min(MAX_CHANGE_AGE); - - ticks_since_system > ticks_since_change + self.changed.is_older_than(last_change_tick, change_tick) } pub(crate) fn new(change_tick: u32) -> Self { Self { - added: change_tick, - changed: change_tick, + added: Tick::new(change_tick), + changed: Tick::new(change_tick), } } - pub(crate) fn check_ticks(&mut self, change_tick: u32) { - check_tick(&mut self.added, change_tick); - check_tick(&mut self.changed, change_tick); - } - /// Manually sets the change tick. /// /// This is normally done automatically via the [`DerefMut`](std::ops::DerefMut) implementation @@ -585,15 +668,6 @@ impl ComponentTicks { /// ``` #[inline] pub fn set_changed(&mut self, change_tick: u32) { - self.changed = change_tick; - } -} - -fn check_tick(last_change_tick: &mut u32, change_tick: u32) { - let age = change_tick.wrapping_sub(*last_change_tick); - // This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true - // so long as this check always runs before that can happen. - if age > MAX_CHANGE_AGE { - *last_change_tick = change_tick.wrapping_sub(MAX_CHANGE_AGE); + self.changed.set_changed(change_tick); } } diff --git a/crates/bevy_ecs/src/entity/map_entities.rs b/crates/bevy_ecs/src/entity/map_entities.rs index ae2d0476c44bc..2a8dbfbc468c8 100644 --- a/crates/bevy_ecs/src/entity/map_entities.rs +++ b/crates/bevy_ecs/src/entity/map_entities.rs @@ -2,6 +2,7 @@ use crate::entity::Entity; use bevy_utils::{Entry, HashMap}; use std::fmt; +/// The errors that might be returned while using [`MapEntities::map_entities`]. #[derive(Debug)] pub enum MapEntitiesError { EntityNotFound(Entity), @@ -19,7 +20,42 @@ impl fmt::Display for MapEntitiesError { } } +/// Operation to map all contained [`Entity`] fields in a type to new values. +/// +/// As entity IDs are valid only for the [`World`] they're sourced from, using [`Entity`] +/// as references in components copied from another world will be invalid. This trait +/// allows defining custom mappings for these references via [`EntityMap`]. +/// +/// Implementing this trait correctly is required for properly loading components +/// with entity references from scenes. +/// +/// ## Example +/// +/// ```rust +/// use bevy_ecs::prelude::*; +/// use bevy_ecs::entity::{EntityMap, MapEntities, MapEntitiesError}; +/// +/// #[derive(Component)] +/// struct Spring { +/// a: Entity, +/// b: Entity, +/// } +/// +/// impl MapEntities for Spring { +/// fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError> { +/// self.a = entity_map.get(self.a)?; +/// self.b = entity_map.get(self.b)?; +/// Ok(()) +/// } +/// } +/// ``` +/// +/// [`World`]: crate::world::World pub trait MapEntities { + /// Updates all [`Entity`] references stored inside using `entity_map`. + /// + /// Implementors should look up any and all [`Entity`] values stored within and + /// update them to the mapped values via `entity_map`. fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError>; } @@ -81,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 844885413716e..c7f74c5f8d052 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -13,7 +13,7 @@ //! //! |Operation|Command|Method| //! |:---:|:---:|:---:| -//! |Spawn an entity with components|[`Commands::spawn`]|---| +//! |Spawn an entity with components|[`Commands::spawn`]|[`World::spawn`]| //! |Spawn an entity without components|[`Commands::spawn_empty`]|[`World::spawn_empty`]| //! |Despawn an entity|[`EntityCommands::despawn`]|[`World::despawn`]| //! |Insert a component, bundle, or tuple of components and bundles to an entity|[`EntityCommands::insert`]|[`EntityMut::insert`]| @@ -34,7 +34,10 @@ mod map_entities; pub use map_entities::*; -use crate::{archetype::ArchetypeId, storage::SparseSetIndex}; +use crate::{ + archetype::{ArchetypeId, ArchetypeRow}, + storage::{SparseSetIndex, TableId, TableRow}, +}; use serde::{Deserialize, Serialize}; use std::{convert::TryFrom, fmt, mem, sync::atomic::Ordering}; @@ -56,6 +59,9 @@ type IdCursor = isize; /// The identifier is implemented using a [generational index]: a combination of an index and a generation. /// This allows fast insertion after data removal in an array while minimizing loss of spatial locality. /// +/// These identifiers are only valid on the [`World`] it's sourced from. Attempting to use an `Entity` to +/// fetch entity components or metadata from a different world will either fail or return unexpected results. +/// /// [generational index]: https://lucassardois.medium.com/generational-indices-guide-8e3c5f7fd594 /// /// # Usage @@ -103,34 +109,27 @@ type IdCursor = isize; /// [`EntityMut::id`]: crate::world::EntityMut::id /// [`EntityCommands`]: crate::system::EntityCommands /// [`Query::get`]: crate::system::Query::get +/// [`World`]: crate::world::World #[derive(Clone, Copy, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] pub struct Entity { - pub(crate) generation: u32, - pub(crate) index: u32, + generation: u32, + index: u32, } -pub enum AllocAtWithoutReplacement { +pub(crate) enum AllocAtWithoutReplacement { Exists(EntityLocation), DidNotExist, ExistsWithWrongGeneration, } impl Entity { - /// Creates a new entity reference with the specified `index` and a generation of 0. - /// - /// # Note - /// - /// Spawning a specific `entity` value is __rarely the right choice__. Most apps should favor - /// [`Commands::spawn`](crate::system::Commands::spawn). This method should generally - /// only be used for sharing entities across apps, and only when they have a scheme - /// worked out to share an index space (which doesn't happen by default). - /// - /// In general, one should not try to synchronize the ECS by attempting to ensure that - /// `Entity` lines up between instances, but instead insert a secondary identifier as - /// a component. - /// - /// There are still some use cases where it might be appropriate to use this function - /// externally. + #[cfg(test)] + pub(crate) const fn new(index: u32, generation: u32) -> Entity { + Entity { index, generation } + } + + /// An entity ID with a placeholder value. This may or may not correspond to an actual entity, + /// and should be overwritten by a new value before being used. /// /// ## Examples /// @@ -138,8 +137,8 @@ impl Entity { /// /// ```no_run /// # use bevy_ecs::prelude::*; - /// // Create a new array of size 10 and initialize it with (invalid) entities. - /// let mut entities: [Entity; 10] = [Entity::from_raw(0); 10]; + /// // Create a new array of size 10 filled with invalid entity ids. + /// let mut entities: [Entity; 10] = [Entity::PLACEHOLDER; 10]; /// /// // ... replace the entities with valid ones. /// ``` @@ -158,11 +157,25 @@ impl Entity { /// impl FromWorld for MyStruct { /// fn from_world(_world: &mut World) -> Self { /// Self { - /// entity: Entity::from_raw(u32::MAX), + /// entity: Entity::PLACEHOLDER, /// } /// } /// } /// ``` + pub const PLACEHOLDER: Self = Self::from_raw(u32::MAX); + + /// Creates a new entity ID with the specified `index` and a generation of 0. + /// + /// # Note + /// + /// Spawning a specific `entity` value is __rarely the right choice__. Most apps should favor + /// [`Commands::spawn`](crate::system::Commands::spawn). This method should generally + /// only be used for sharing entities across apps, and only when they have a scheme + /// worked out to share an index space (which doesn't happen by default). + /// + /// In general, one should not try to synchronize the ECS by attempting to ensure that + /// `Entity` lines up between instances, but instead insert a secondary identifier as + /// a component. pub const fn from_raw(index: u32) -> Entity { Entity { index, @@ -265,9 +278,17 @@ impl<'a> Iterator for ReserveEntitiesIterator<'a> { impl<'a> core::iter::ExactSizeIterator for ReserveEntitiesIterator<'a> {} impl<'a> core::iter::FusedIterator for ReserveEntitiesIterator<'a> {} -#[derive(Debug, Default)] +/// A [`World`]'s internal metadata store on all of its entities. +/// +/// Contains metadata on: +/// - The generation of every entity. +/// - The alive/dead status of a particular entity. (i.e. "has entity 3 been despawned?") +/// - The location of the entity's components in memory (via [`EntityLocation`]) +/// +/// [`World`]: crate::world::World +#[derive(Debug)] pub struct Entities { - pub(crate) meta: Vec, + meta: Vec, /// The `pending` and `free_cursor` fields describe three sets of Entity IDs /// that have been freed or are in the process of being allocated: @@ -280,8 +301,8 @@ pub struct Entities { /// `reserve_entities` or `reserve_entity()`. They are now waiting for `flush()` to make them /// fully allocated. /// - /// - The count of new IDs that do not yet exist in `self.meta()`, but which we have handed out - /// and reserved. `flush()` will allocate room for them in `self.meta()`. + /// - The count of new IDs that do not yet exist in `self.meta`, but which we have handed out + /// and reserved. `flush()` will allocate room for them in `self.meta`. /// /// The contents of `pending` look like this: /// @@ -311,6 +332,15 @@ pub struct Entities { } impl Entities { + pub(crate) const fn new() -> Self { + Entities { + meta: Vec::new(), + pending: Vec::new(), + free_cursor: AtomicIdCursor::new(0), + len: 0, + } + } + /// Reserve entity IDs concurrently. /// /// Storage for entity generation and location is lazily allocated by calling `flush`. @@ -447,7 +477,10 @@ impl Entities { /// Allocate a specific entity ID, overwriting its generation. /// /// Returns the location of the entity currently using the given ID, if any. - pub fn alloc_at_without_replacement(&mut self, entity: Entity) -> AllocAtWithoutReplacement { + pub(crate) fn alloc_at_without_replacement( + &mut self, + entity: Entity, + ) -> AllocAtWithoutReplacement { self.verify_flushed(); let result = if entity.index as usize >= self.meta.len() { @@ -522,6 +555,7 @@ impl Entities { .map_or(false, |e| e.generation() == entity.generation) } + /// Clears all [`Entity`] from the World. pub fn clear(&mut self) { self.meta.clear(); self.pending.clear(); @@ -544,6 +578,18 @@ impl Entities { } } + /// Updates the location of an [`Entity`]. This must be called when moving the components of + /// the entity around in storage. + /// + /// # Safety + /// - `index` must be a valid entity index. + /// - `location` must be valid for the entity at `index` or immediately made valid afterwards + /// before handing control to unknown code. + pub(crate) unsafe fn set(&mut self, index: u32, location: EntityLocation) { + // SAFETY: Caller guarentees that `index` a valid entity index + self.meta.get_unchecked_mut(index as usize).location = location; + } + /// Get the [`Entity`] with a given id, if it exists in this [`Entities`] collection /// Returns `None` if this [`Entity`] is outside of the range of currently reserved Entities /// @@ -645,29 +691,42 @@ impl Entities { self.len = count as u32; } - /// Accessor for getting the length of the vec in `self.meta` + /// The count of all entities in the [`World`] that have ever been allocated + /// including the entities that are currently freed. + /// + /// This does not include entities that have been reserved but have never been + /// allocated yet. + /// + /// [`World`]: crate::world::World #[inline] - pub fn meta_len(&self) -> usize { + pub fn total_count(&self) -> usize { self.meta.len() } + /// The count of currently allocated entities. #[inline] pub fn len(&self) -> u32 { self.len } + /// Checks if any entity is currently active. #[inline] pub fn is_empty(&self) -> bool { self.len == 0 } } +// 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)] -pub struct EntityMeta { +struct EntityMeta { + /// The current generation of the [`Entity`]. pub generation: u32, + /// The current location of the [`Entity`] pub location: EntityLocation, } @@ -676,20 +735,40 @@ impl EntityMeta { generation: 0, location: EntityLocation { archetype_id: ArchetypeId::INVALID, - index: usize::MAX, // dummy value, to be filled in + 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 - pub index: usize, + /// 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)] @@ -707,7 +786,7 @@ mod tests { #[test] fn reserve_entity_len() { - let mut e = Entities::default(); + let mut e = Entities::new(); e.reserve_entity(); // SAFETY: entity_location is left invalid unsafe { e.flush(|_, _| {}) }; @@ -716,7 +795,7 @@ mod tests { #[test] fn get_reserved_and_invalid() { - let mut entities = Entities::default(); + let mut entities = Entities::new(); let e = entities.reserve_entity(); assert!(entities.contains(e)); assert!(entities.get(e).is_none()); diff --git a/crates/bevy_ecs/src/event.rs b/crates/bevy_ecs/src/event.rs index 1c46c90dc0353..2b641bf48216b 100644 --- a/crates/bevy_ecs/src/event.rs +++ b/crates/bevy_ecs/src/event.rs @@ -2,10 +2,9 @@ use crate as bevy_ecs; use crate::system::{Local, Res, ResMut, Resource, SystemParam}; -use bevy_utils::tracing::{trace, warn}; +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. @@ -226,7 +220,7 @@ impl<'w, 's, E: Event> EventReader<'w, 's, E> { /// # /// struct CollisionEvent; /// - /// fn play_collision_sound(events: EventReader) { + /// fn play_collision_sound(mut events: EventReader) { /// if !events.is_empty() { /// events.clear(); /// // Play a sound @@ -246,7 +240,7 @@ impl<'w, 's, E: Event> EventReader<'w, 's, E> { /// In those situations you generally want to consume those events to make sure they don't appear in the next frame. /// /// For more information see [`EventReader::is_empty()`]. - pub fn clear(mut self) { + pub fn clear(&mut self) { self.iter().last(); } } @@ -295,13 +289,11 @@ impl<'w, 's, E: Event> EventReader<'w, 's, E> { /// ``` /// Note that this is considered *non-idiomatic*, and should only be used when `EventWriter` will not work. #[derive(SystemParam)] -pub struct EventWriter<'w, 's, E: Event> { +pub struct EventWriter<'w, E: Event> { events: ResMut<'w, Events>, - #[system_param(ignore)] - marker: PhantomData<&'s usize>, } -impl<'w, 's, E: Event> EventWriter<'w, 's, E> { +impl<'w, E: Event> EventWriter<'w, E> { /// Sends an `event`. [`EventReader`]s can then read the event. /// See [`Events`] for details. pub fn send(&mut self, event: E) { @@ -339,43 +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)> { - // if the reader has seen some of the events in a buffer, find the proper index offset. - // otherwise read all events in the buffer - let missed = self.missed_events(events); - if missed > 0 { - let plural = if missed == 1 { "event" } else { "events" }; - let type_name = std::any::type_name::(); - warn!("Missed {missed} `{type_name}` {plural}. Consider reading from the `EventReader` more often (generally the best solution) or calling Events::update() less frequently (normally this is called once per frame). This problem is most likely due to run criteria/fixed timesteps or consuming events conditionally. See the Events documentation for more information."); - } - - 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`] @@ -403,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, +} - #[inline] - fn next(&mut self) -> Option { - self.iter.next().map(|e| { - self.len -= 1; - e - }) +fn event_trace(id: EventId) { + trace!("EventReader::iter() -> {}", id); +} + +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 } } @@ -559,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 { @@ -828,7 +878,7 @@ mod tests { events.send(TestEvent { i: 0 }); world.insert_resource(events); - let mut reader = IntoSystem::into_system(|events: EventReader| -> bool { + let mut reader = IntoSystem::into_system(|mut events: EventReader| -> bool { if !events.is_empty() { events.clear(); false diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index a91ed6634f1b6..46f1a8dc4281c 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -38,9 +38,10 @@ pub mod prelude { Schedule, Stage, StageLabel, State, SystemLabel, SystemSet, SystemStage, }, system::{ - adapter as system_adapter, Commands, In, IntoPipeSystem, IntoSystem, Local, NonSend, - NonSendMut, ParallelCommands, ParamSet, Query, RemovedComponents, Res, ResMut, - Resource, System, SystemParamFunction, + adapter as system_adapter, + adapter::{dbg, error, ignore, info, unwrap, warn}, + Commands, In, IntoPipeSystem, IntoSystem, Local, NonSend, NonSendMut, ParallelCommands, + ParamSet, Query, RemovedComponents, Res, ResMut, Resource, System, SystemParamFunction, }, world::{FromWorld, Mut, World}, }; @@ -960,6 +961,90 @@ mod tests { assert_eq!(get_filtered::>(&mut world), vec![e4]); } + #[test] + fn changed_trackers_sparse() { + let mut world = World::default(); + let e1 = world.spawn(SparseStored(0)).id(); + let e2 = world.spawn(SparseStored(0)).id(); + let e3 = world.spawn(SparseStored(0)).id(); + world.spawn(SparseStored(0)); + + world.clear_trackers(); + + for (i, mut a) in world + .query::<&mut SparseStored>() + .iter_mut(&mut world) + .enumerate() + { + if i % 2 == 0 { + a.0 += 1; + } + } + + fn get_filtered(world: &mut World) -> Vec { + world + .query_filtered::() + .iter(world) + .collect::>() + } + + assert_eq!( + get_filtered::>(&mut world), + vec![e1, e3] + ); + + // ensure changing an entity's archetypes also moves its changed state + world.entity_mut(e1).insert(C); + + assert_eq!(get_filtered::>(&mut world), vec![e3, e1], "changed entities list should not change (although the order will due to archetype moves)"); + + // spawning a new SparseStored entity should not change existing changed state + world.entity_mut(e1).insert(SparseStored(0)); + assert_eq!( + get_filtered::>(&mut world), + vec![e3, e1], + "changed entities list should not change" + ); + + // removing an unchanged entity should not change changed state + assert!(world.despawn(e2)); + assert_eq!( + get_filtered::>(&mut world), + vec![e3, e1], + "changed entities list should not change" + ); + + // removing a changed entity should remove it from enumeration + assert!(world.despawn(e1)); + assert_eq!( + get_filtered::>(&mut world), + vec![e3], + "e1 should no longer be returned" + ); + + world.clear_trackers(); + + assert!(get_filtered::>(&mut world).is_empty()); + + let e4 = world.spawn_empty().id(); + + world.entity_mut(e4).insert(SparseStored(0)); + assert_eq!(get_filtered::>(&mut world), vec![e4]); + assert_eq!(get_filtered::>(&mut world), vec![e4]); + + world.entity_mut(e4).insert(A(1)); + assert_eq!(get_filtered::>(&mut world), vec![e4]); + + world.clear_trackers(); + + // ensure inserting multiple components set changed state for all components and set added + // state for non existing components even when changing archetype. + world.entity_mut(e4).insert(SparseStored(0)); + + assert!(get_filtered::>(&mut world).is_empty()); + assert_eq!(get_filtered::>(&mut world), vec![e4]); + } + #[test] fn empty_spawn() { let mut world = World::default(); @@ -1129,7 +1214,7 @@ mod tests { } #[test] - fn remove_bundle() { + fn remove() { let mut world = World::default(); world.spawn((A(1), B(1), TableStored("1"))); let e2 = world.spawn((A(2), B(2), TableStored("2"))).id(); @@ -1465,19 +1550,14 @@ mod tests { let e3 = world_a.entities().reserve_entity(); world_a.flush(); - let world_a_max_entities = world_a.entities().meta.len(); - world_b - .entities - .reserve_entities(world_a_max_entities as u32); + let world_a_max_entities = world_a.entities().len(); + world_b.entities.reserve_entities(world_a_max_entities); world_b.entities.flush_as_invalid(); let e4 = world_b.spawn(A(4)).id(); assert_eq!( e4, - Entity { - generation: 0, - index: 3, - }, + Entity::new(3, 0), "new entity is created immediately after world_a's max entity" ); assert!(world_b.get::(e1).is_none()); @@ -1508,10 +1588,7 @@ mod tests { "spawning into existing `world_b` entities works" ); - let e4_mismatched_generation = Entity { - generation: 1, - index: 3, - }; + let e4_mismatched_generation = Entity::new(3, 1); assert!( world_b.get_or_spawn(e4_mismatched_generation).is_none(), "attempting to spawn on top of an entity with a mismatched entity generation fails" @@ -1527,10 +1604,7 @@ mod tests { "failed mismatched spawn doesn't change existing entity" ); - let high_non_existent_entity = Entity { - generation: 0, - index: 6, - }; + let high_non_existent_entity = Entity::new(6, 0); world_b .get_or_spawn(high_non_existent_entity) .unwrap() @@ -1541,10 +1615,7 @@ mod tests { "inserting into newly allocated high / non-continous entity id works" ); - let high_non_existent_but_reserved_entity = Entity { - generation: 0, - index: 5, - }; + let high_non_existent_but_reserved_entity = Entity::new(5, 0); assert!( world_b.get_entity(high_non_existent_but_reserved_entity).is_none(), "entities between high-newly allocated entity and continuous block of existing entities don't exist" @@ -1560,22 +1631,10 @@ mod tests { assert_eq!( reserved_entities, vec![ - Entity { - generation: 0, - index: 5 - }, - Entity { - generation: 0, - index: 4 - }, - Entity { - generation: 0, - index: 7, - }, - Entity { - generation: 0, - index: 8, - }, + Entity::new(5, 0), + Entity::new(4, 0), + Entity::new(7, 0), + Entity::new(8, 0), ], "space between original entities and high entities is used for new entity ids" ); @@ -1624,10 +1683,7 @@ mod tests { let e0 = world.spawn(A(0)).id(); let e1 = Entity::from_raw(1); let e2 = world.spawn_empty().id(); - let invalid_e2 = Entity { - generation: 1, - index: e2.index, - }; + let invalid_e2 = Entity::new(e2.index(), 1); let values = vec![(e0, (B(0), C)), (e1, (B(1), C)), (invalid_e2, (B(2), C))]; diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index d9580bd4eb507..ea18adf841073 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -1,13 +1,51 @@ use crate::storage::SparseSetIndex; use bevy_utils::HashSet; +use core::fmt; use fixedbitset::FixedBitSet; use std::marker::PhantomData; +/// A wrapper struct to make Debug representations of [`FixedBitSet`] easier +/// to read, when used to store [`SparseSetIndex`]. +/// +/// Instead of the raw integer representation of the `FixedBitSet`, the list of +/// `T` valid for [`SparseSetIndex`] is shown. +/// +/// Normal `FixedBitSet` `Debug` output: +/// ```text +/// read_and_writes: FixedBitSet { data: [ 160 ], length: 8 } +/// ``` +/// +/// Which, unless you are a computer, doesn't help much understand what's in +/// the set. With `FormattedBitSet`, we convert the present set entries into +/// what they stand for, it is much clearer what is going on: +/// ```text +/// read_and_writes: [ ComponentId(5), ComponentId(7) ] +/// ``` +struct FormattedBitSet<'a, T: SparseSetIndex> { + bit_set: &'a FixedBitSet, + _marker: PhantomData, +} +impl<'a, T: SparseSetIndex> FormattedBitSet<'a, T> { + fn new(bit_set: &'a FixedBitSet) -> Self { + Self { + bit_set, + _marker: PhantomData, + } + } +} +impl<'a, T: SparseSetIndex + fmt::Debug> fmt::Debug for FormattedBitSet<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list() + .entries(self.bit_set.ones().map(T::get_sparse_set_index)) + .finish() + } +} + /// Tracks read and write access to specific elements in a collection. /// /// Used internally to ensure soundness during system initialization and execution. /// See the [`is_compatible`](Access::is_compatible) and [`get_conflicts`](Access::get_conflicts) functions. -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Clone, Eq, PartialEq)] pub struct Access { /// All accessed elements. reads_and_writes: FixedBitSet, @@ -19,18 +57,35 @@ pub struct Access { marker: PhantomData, } +impl fmt::Debug for Access { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Access") + .field( + "read_and_writes", + &FormattedBitSet::::new(&self.reads_and_writes), + ) + .field("writes", &FormattedBitSet::::new(&self.writes)) + .field("reads_all", &self.reads_all) + .finish() + } +} 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. @@ -55,11 +110,7 @@ impl Access { /// Returns `true` if this can access the element given by `index`. pub fn has_read(&self, index: T) -> bool { - if self.reads_all { - true - } else { - self.reads_and_writes.contains(index.sparse_set_index()) - } + self.reads_all || self.reads_and_writes.contains(index.sparse_set_index()) } /// Returns `true` if this can exclusively access the element given by `index`. @@ -106,7 +157,7 @@ impl Access { } self.writes.is_disjoint(&other.reads_and_writes) - && self.reads_and_writes.is_disjoint(&other.writes) + && other.writes.is_disjoint(&self.reads_and_writes) } /// Returns a vector of elements that the access and `other` cannot access at the same time. @@ -153,7 +204,7 @@ impl Access { /// `with` access. /// /// For example consider `Query>` this only has a `read` of `T` as doing -/// otherwise would allow for queries to be considered disjoint that actually aren't: +/// otherwise would allow for queries to be considered disjoint when they shouldn't: /// - `Query<(&mut T, Option<&U>)>` read/write `T`, read `U`, with `U` /// - `Query<&mut T, Without>` read/write `T`, without `U` /// from this we could reasonably conclude that the queries are disjoint but they aren't. @@ -165,12 +216,21 @@ impl Access { /// - `Query` accesses nothing /// /// See comments the `WorldQuery` impls of `AnyOf`/`Option`/`Or` for more information. -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Clone, Eq, PartialEq)] pub struct FilteredAccess { access: Access, with: FixedBitSet, without: FixedBitSet, } +impl fmt::Debug for FilteredAccess { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FilteredAccess") + .field("access", &self.access) + .field("with", &FormattedBitSet::::new(&self.with)) + .field("without", &FormattedBitSet::::new(&self.without)) + .finish() + } +} impl Default for FilteredAccess { fn default() -> Self { @@ -238,12 +298,9 @@ impl FilteredAccess { /// Returns `true` if this and `other` can be active at the same time. pub fn is_compatible(&self, other: &FilteredAccess) -> bool { - if self.access.is_compatible(&other.access) { - true - } else { - self.with.intersection(&other.without).next().is_some() - || self.without.intersection(&other.with).next().is_some() - } + self.access.is_compatible(&other.access) + || !self.with.is_disjoint(&other.without) + || !other.with.is_disjoint(&self.without) } /// Returns a vector of elements that this and `other` cannot access at the same time. @@ -271,6 +328,10 @@ impl FilteredAccess { /// A collection of [`FilteredAccess`] instances. /// /// Used internally to statically check if systems have conflicting access. +/// +/// It stores multiple sets of accesses. +/// - A "combined" set, which is the access of all filters in this set combined. +/// - The set of access of each individual filters in this set. #[derive(Debug, Clone)] pub struct FilteredAccessSet { combined_access: Access, @@ -284,13 +345,18 @@ impl FilteredAccessSet { &self.combined_access } - /// Returns a mutable reference to the unfiltered access of the entire set. - #[inline] - pub fn combined_access_mut(&mut self) -> &mut Access { - &mut self.combined_access - } - /// Returns `true` if this and `other` can be active at the same time. + /// + /// Access conflict resolution happen in two steps: + /// 1. A "coarse" check, if there is no mutual unfiltered conflict between + /// `self` and `other`, we already know that the two access sets are + /// compatible. + /// 2. A "fine grained" check, it kicks in when the "coarse" check fails. + /// the two access sets might still be compatible if some of the accesses + /// are restricted with the `With` or `Without` filters so that access is + /// mutually exclusive. The fine grained phase iterates over all filters in + /// the `self` set and compares it to all the filters in the `other` set, + /// making sure they are all mutually compatible. pub fn is_compatible(&self, other: &FilteredAccessSet) -> bool { if self.combined_access.is_compatible(other.combined_access()) { return true; @@ -302,7 +368,6 @@ impl FilteredAccessSet { } } } - true } @@ -338,6 +403,20 @@ impl FilteredAccessSet { self.filtered_accesses.push(filtered_access); } + /// Adds a read access without filters to the set. + pub(crate) fn add_unfiltered_read(&mut self, index: T) { + let mut filter = FilteredAccess::default(); + filter.add_read(index); + self.add(filter); + } + + /// Adds a write access without filters to the set. + pub(crate) fn add_unfiltered_write(&mut self, index: T) { + let mut filter = FilteredAccess::default(); + filter.add_write(index); + self.add(filter); + } + pub fn extend(&mut self, filtered_access_set: FilteredAccessSet) { self.combined_access .extend(&filtered_access_set.combined_access); @@ -362,7 +441,30 @@ impl Default for FilteredAccessSet { #[cfg(test)] mod tests { - use crate::query::{Access, FilteredAccess}; + use crate::query::{Access, FilteredAccess, FilteredAccessSet}; + + #[test] + fn read_all_access_conflicts() { + // read_all / single write + let mut access_a = Access::::default(); + access_a.grow(10); + access_a.add_write(0); + + let mut access_b = Access::::default(); + access_b.read_all(); + + assert!(!access_b.is_compatible(&access_a)); + + // read_all / read_all + let mut access_a = Access::::default(); + access_a.grow(10); + access_a.read_all(); + + let mut access_b = Access::::default(); + access_b.read_all(); + + assert!(access_b.is_compatible(&access_a)); + } #[test] fn access_get_conflicts() { @@ -391,6 +493,22 @@ mod tests { assert_eq!(access_d.get_conflicts(&access_c), vec![0]); } + #[test] + fn filtered_combined_access() { + let mut access_a = FilteredAccessSet::::default(); + access_a.add_unfiltered_read(1); + + let mut filter_b = FilteredAccess::::default(); + filter_b.add_write(1); + + let conflicts = access_a.get_conflicts_single(&filter_b); + assert_eq!( + &conflicts, + &[1_usize], + "access_a: {access_a:?}, filter_b: {filter_b:?}" + ); + } + #[test] fn filtered_access_extend() { let mut access_a = FilteredAccess::::default(); diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index ad6598bcc6e0d..4a3f49f302d95 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1,10 +1,10 @@ use crate::{ archetype::{Archetype, ArchetypeComponentId}, change_detection::Ticks, - component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType}, + component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType, Tick}, entity::Entity, query::{Access, DebugCheckedUnwrap, FilteredAccess}, - storage::{ComponentSparseSet, Table}, + storage::{ComponentSparseSet, Table, TableRow}, world::{Mut, World}, }; use bevy_ecs_macros::all_tuples; @@ -342,7 +342,7 @@ pub unsafe trait WorldQuery { /// # Safety /// While calling this method on its own cannot cause UB it is marked `unsafe` as the caller must ensure /// that the returned value is not used in any way that would cause two `QueryItem` for the same - /// `archetype_index` or `table_row` to be alive at the same time. + /// `archetype_row` or `table_row` to be alive at the same time. unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w>; /// Returns true if (and only if) every table of every archetype matched by this fetch contains @@ -395,7 +395,7 @@ pub unsafe trait WorldQuery { unsafe fn fetch<'w>( fetch: &mut Self::Fetch<'w>, entity: Entity, - table_row: usize, + table_row: TableRow, ) -> Self::Item<'w>; /// # Safety @@ -404,7 +404,11 @@ pub unsafe trait WorldQuery { /// `table_row` must be in the range of the current table and archetype. #[allow(unused_variables)] #[inline(always)] - unsafe fn filter_fetch(fetch: &mut Self::Fetch<'_>, entity: Entity, table_row: usize) -> bool { + unsafe fn filter_fetch( + fetch: &mut Self::Fetch<'_>, + entity: Entity, + table_row: TableRow, + ) -> bool { true } @@ -484,7 +488,7 @@ unsafe impl WorldQuery for Entity { unsafe fn fetch<'w>( _fetch: &mut Self::Fetch<'w>, entity: Entity, - _table_row: usize, + _table_row: TableRow, ) -> Self::Item<'w> { entity } @@ -595,13 +599,13 @@ unsafe impl WorldQuery for &T { unsafe fn fetch<'w>( fetch: &mut Self::Fetch<'w>, entity: Entity, - table_row: usize, + table_row: TableRow, ) -> Self::Item<'w> { match T::Storage::STORAGE_TYPE { StorageType::Table => fetch .table_components .debug_checked_unwrap() - .get(table_row) + .get(table_row.index()) .deref(), StorageType::SparseSet => fetch .sparse_set @@ -654,7 +658,8 @@ pub struct WriteFetch<'w, T> { // T::Storage = TableStorage table_data: Option<( ThinSlicePtr<'w, UnsafeCell>, - ThinSlicePtr<'w, UnsafeCell>, + ThinSlicePtr<'w, UnsafeCell>, + ThinSlicePtr<'w, UnsafeCell>, )>, // T::Storage = SparseStorage sparse_set: Option<&'w ComponentSparseSet>, @@ -733,7 +738,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { let column = table.get_column(component_id).debug_checked_unwrap(); fetch.table_data = Some(( column.get_data_slice().into(), - column.get_ticks_slice().into(), + column.get_added_ticks_slice().into(), + column.get_changed_ticks_slice().into(), )); } @@ -741,33 +747,31 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { unsafe fn fetch<'w>( fetch: &mut Self::Fetch<'w>, entity: Entity, - table_row: usize, + table_row: TableRow, ) -> Self::Item<'w> { match T::Storage::STORAGE_TYPE { StorageType::Table => { - let (table_components, table_ticks) = fetch.table_data.debug_checked_unwrap(); + let (table_components, added_ticks, changed_ticks) = + fetch.table_data.debug_checked_unwrap(); Mut { - value: table_components.get(table_row).deref_mut(), + value: table_components.get(table_row.index()).deref_mut(), ticks: Ticks { - component_ticks: table_ticks.get(table_row).deref_mut(), + added: added_ticks.get(table_row.index()).deref_mut(), + changed: changed_ticks.get(table_row.index()).deref_mut(), change_tick: fetch.change_tick, last_change_tick: fetch.last_change_tick, }, } } StorageType::SparseSet => { - let (component, component_ticks) = fetch + let (component, ticks) = fetch .sparse_set .debug_checked_unwrap() .get_with_ticks(entity) .debug_checked_unwrap(); Mut { value: component.assert_unique().deref_mut(), - ticks: Ticks { - component_ticks: component_ticks.deref_mut(), - change_tick: fetch.change_tick, - last_change_tick: fetch.last_change_tick, - }, + ticks: Ticks::from_tick_cells(ticks, fetch.last_change_tick, fetch.change_tick), } } } @@ -872,7 +876,7 @@ unsafe impl WorldQuery for Option { unsafe fn fetch<'w>( fetch: &mut Self::Fetch<'w>, entity: Entity, - table_row: usize, + table_row: TableRow, ) -> Self::Item<'w> { fetch .matches @@ -992,7 +996,8 @@ impl ChangeTrackers { #[doc(hidden)] pub struct ChangeTrackersFetch<'w, T> { // T::Storage = TableStorage - table_ticks: Option>>, + table_added: Option>>, + table_changed: Option>>, // T::Storage = SparseStorage sparse_set: Option<&'w ComponentSparseSet>, @@ -1028,7 +1033,8 @@ unsafe impl WorldQuery for ChangeTrackers { change_tick: u32, ) -> ChangeTrackersFetch<'w, T> { ChangeTrackersFetch { - table_ticks: None, + table_added: None, + table_changed: None, sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { world .storages() @@ -1044,7 +1050,8 @@ unsafe impl WorldQuery for ChangeTrackers { unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { ChangeTrackersFetch { - table_ticks: fetch.table_ticks, + table_added: fetch.table_added, + table_changed: fetch.table_changed, sparse_set: fetch.sparse_set, marker: fetch.marker, last_change_tick: fetch.last_change_tick, @@ -1070,38 +1077,43 @@ unsafe impl WorldQuery for ChangeTrackers { &id: &ComponentId, table: &'w Table, ) { - fetch.table_ticks = Some( - table - .get_column(id) - .debug_checked_unwrap() - .get_ticks_slice() - .into(), - ); + let column = table.get_column(id).debug_checked_unwrap(); + fetch.table_added = Some(column.get_added_ticks_slice().into()); + fetch.table_changed = Some(column.get_changed_ticks_slice().into()); } #[inline(always)] unsafe fn fetch<'w>( fetch: &mut Self::Fetch<'w>, entity: Entity, - table_row: usize, + table_row: TableRow, ) -> Self::Item<'w> { match T::Storage::STORAGE_TYPE { StorageType::Table => ChangeTrackers { component_ticks: { - let table_ticks = fetch.table_ticks.debug_checked_unwrap(); - table_ticks.get(table_row).read() + ComponentTicks { + added: fetch + .table_added + .debug_checked_unwrap() + .get(table_row.index()) + .read(), + changed: fetch + .table_changed + .debug_checked_unwrap() + .get(table_row.index()) + .read(), + } }, marker: PhantomData, last_change_tick: fetch.last_change_tick, change_tick: fetch.change_tick, }, StorageType::SparseSet => ChangeTrackers { - component_ticks: *fetch + component_ticks: fetch .sparse_set .debug_checked_unwrap() .get_ticks(entity) - .debug_checked_unwrap() - .get(), + .debug_checked_unwrap(), marker: PhantomData, last_change_tick: fetch.last_change_tick, change_tick: fetch.change_tick, @@ -1202,7 +1214,7 @@ macro_rules! impl_tuple_fetch { unsafe fn fetch<'w>( _fetch: &mut Self::Fetch<'w>, _entity: Entity, - _table_row: usize + _table_row: TableRow ) -> Self::Item<'w> { let ($($name,)*) = _fetch; ($($name::fetch($name, _entity, _table_row),)*) @@ -1212,7 +1224,7 @@ macro_rules! impl_tuple_fetch { unsafe fn filter_fetch<'w>( _fetch: &mut Self::Fetch<'w>, _entity: Entity, - _table_row: usize + _table_row: TableRow ) -> bool { let ($($name,)*) = _fetch; true $(&& $name::filter_fetch($name, _entity, _table_row))* @@ -1321,7 +1333,7 @@ macro_rules! impl_anytuple_fetch { unsafe fn fetch<'w>( _fetch: &mut Self::Fetch<'w>, _entity: Entity, - _table_row: usize + _table_row: TableRow ) -> Self::Item<'w> { let ($($name,)*) = _fetch; ($( @@ -1435,7 +1447,7 @@ unsafe impl WorldQuery for NopWorldQuery { unsafe fn fetch<'w>( _fetch: &mut Self::Fetch<'w>, _entity: Entity, - _table_row: usize, + _table_row: TableRow, ) -> Self::Item<'w> { } diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index 2cf2fc3e1c10b..a067acbd89932 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -1,9 +1,9 @@ use crate::{ archetype::{Archetype, ArchetypeComponentId}, - component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType}, + component::{Component, ComponentId, ComponentStorage, StorageType, Tick}, entity::Entity, query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery}, - storage::{ComponentSparseSet, Table}, + storage::{Column, ComponentSparseSet, Table, TableRow}, world::World, }; use bevy_ecs_macros::all_tuples; @@ -85,7 +85,7 @@ unsafe impl WorldQuery for With { unsafe fn fetch<'w>( _fetch: &mut Self::Fetch<'w>, _entity: Entity, - _table_row: usize, + _table_row: TableRow, ) -> Self::Item<'w> { } @@ -187,7 +187,7 @@ unsafe impl WorldQuery for Without { unsafe fn fetch<'w>( _fetch: &mut Self::Fetch<'w>, _entity: Entity, - _table_row: usize, + _table_row: TableRow, ) -> Self::Item<'w> { } @@ -330,7 +330,7 @@ macro_rules! impl_query_filter_tuple { unsafe fn fetch<'w>( fetch: &mut Self::Fetch<'w>, _entity: Entity, - _table_row: usize + _table_row: TableRow ) -> Self::Item<'w> { let ($($filter,)*) = fetch; false $(|| ($filter.matches && $filter::filter_fetch(&mut $filter.fetch, _entity, _table_row)))* @@ -340,7 +340,7 @@ macro_rules! impl_query_filter_tuple { unsafe fn filter_fetch<'w>( fetch: &mut Self::Fetch<'w>, entity: Entity, - table_row: usize + table_row: TableRow ) -> bool { Self::fetch(fetch, entity, table_row) } @@ -405,7 +405,8 @@ macro_rules! impl_tick_filter { $name: ident, $(#[$fetch_meta:meta])* $fetch_name: ident, - $is_detected: expr + $get_slice: expr, + $get_sparse_set: expr ) => { $(#[$meta])* pub struct $name(PhantomData); @@ -413,7 +414,7 @@ macro_rules! impl_tick_filter { #[doc(hidden)] $(#[$fetch_meta])* pub struct $fetch_name<'w, T> { - table_ticks: Option>>, + table_ticks: Option< ThinSlicePtr<'w, UnsafeCell>>, marker: PhantomData, sparse_set: Option<&'w ComponentSparseSet>, last_change_tick: u32, @@ -475,10 +476,11 @@ macro_rules! impl_tick_filter { table: &'w Table ) { fetch.table_ticks = Some( - table.get_column(component_id) - .debug_checked_unwrap() - .get_ticks_slice() - .into() + $get_slice( + &table + .get_column(component_id) + .debug_checked_unwrap() + ).into(), ); } @@ -498,27 +500,25 @@ macro_rules! impl_tick_filter { unsafe fn fetch<'w>( fetch: &mut Self::Fetch<'w>, entity: Entity, - table_row: usize + table_row: TableRow ) -> Self::Item<'w> { match T::Storage::STORAGE_TYPE { StorageType::Table => { - $is_detected(&*( - fetch.table_ticks + fetch + .table_ticks .debug_checked_unwrap() - .get(table_row)) - .deref(), - fetch.last_change_tick, - fetch.change_tick - ) + .get(table_row.index()) + .deref() + .is_older_than(fetch.last_change_tick, fetch.change_tick) } StorageType::SparseSet => { - let ticks = &*fetch + let sparse_set = &fetch .sparse_set + .debug_checked_unwrap(); + $get_sparse_set(sparse_set, entity) .debug_checked_unwrap() - .get_ticks(entity) - .debug_checked_unwrap() - .get(); - $is_detected(ticks, fetch.last_change_tick, fetch.change_tick) + .deref() + .is_older_than(fetch.last_change_tick, fetch.change_tick) } } } @@ -527,7 +527,7 @@ macro_rules! impl_tick_filter { unsafe fn filter_fetch<'w>( fetch: &mut Self::Fetch<'w>, entity: Entity, - table_row: usize + table_row: TableRow ) -> bool { Self::fetch(fetch, entity, table_row) } @@ -595,7 +595,8 @@ impl_tick_filter!( /// ``` Added, AddedFetch, - ComponentTicks::is_added + Column::get_added_ticks_slice, + ComponentSparseSet::get_added_ticks ); impl_tick_filter!( @@ -632,7 +633,8 @@ impl_tick_filter!( /// ``` Changed, ChangedFetch, - ComponentTicks::is_changed + Column::get_changed_ticks_slice, + ComponentSparseSet::get_changed_ticks ); /// A marker trait to indicate that the filter works at an archetype level. diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 8924c54829a2d..138235b836e19 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -3,7 +3,7 @@ use crate::{ entity::{Entities, Entity}, prelude::World, query::{ArchetypeFilter, DebugCheckedUnwrap, QueryState, WorldQuery}, - storage::{TableId, Tables}, + storage::{TableId, TableRow, Tables}, }; use std::{borrow::Borrow, iter::FusedIterator, marker::PhantomData, mem::MaybeUninit}; @@ -56,13 +56,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Iterator for QueryIter<'w, 's } fn size_hint(&self) -> (usize, Option) { - let max_size = self - .query_state - .matched_archetype_ids - .iter() - .map(|id| self.archetypes[*id].len()) - .sum(); - + let max_size = self.cursor.max_remaining(self.tables, self.archetypes); let archetype_query = Q::IS_ARCHETYPAL && F::IS_ARCHETYPAL; let min_size = if archetype_query { max_size } else { 0 }; (min_size, Some(max_size)) @@ -157,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 @@ -176,12 +170,11 @@ where table, ); - let table_row = archetype.entity_table_row(location.index); // SAFETY: set_archetype was called prior. - // `location.index` 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) { - // SAFETY: set_archetype was called prior, `location.index` is an archetype index in range of the current archetype - return Some(Q::fetch(&mut self.fetch, entity, table_row)); + // `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, 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, location.table_row)); } } None @@ -351,11 +344,16 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery, const K: usize> return None; } - // first, iterate from last to first until next item is found + // PERF: can speed up the following code using `cursor.remaining()` instead of `next_item.is_none()` + // when Q::IS_ARCHETYPAL && F::IS_ARCHETYPAL + // + // let `i` be the index of `c`, the last cursor in `self.cursors` that + // returns `K-i` or more elements. + // Make cursor in index `j` for all `j` in `[i, K)` a copy of `c` advanced `j-i+1` times. + // If no such `c` exists, return `None` 'outer: for i in (0..K).rev() { match self.cursors[i].next(self.tables, self.archetypes, self.query_state) { Some(_) => { - // walk forward up to last element, propagating cursor state forward for j in (i + 1)..K { self.cursors[j] = self.cursors[j - 1].clone_cursor(); match self.cursors[j].next(self.tables, self.archetypes, self.query_state) { @@ -409,36 +407,29 @@ impl<'w, 's, Q: ReadOnlyWorldQuery, F: ReadOnlyWorldQuery, const K: usize> Itera } fn size_hint(&self) -> (usize, Option) { - if K == 0 { - return (0, Some(0)); - } - - let max_size: usize = self - .query_state - .matched_archetype_ids - .iter() - .map(|id| self.archetypes[*id].len()) - .sum(); - - if max_size < K { - return (0, Some(0)); - } - if max_size == K { - return (1, Some(1)); - } - // binomial coefficient: (n ; k) = n! / k!(n-k)! = (n*n-1*...*n-k+1) / k! // See https://en.wikipedia.org/wiki/Binomial_coefficient // See https://blog.plover.com/math/choose.html for implementation // It was chosen to reduce overflow potential. fn choose(n: usize, k: usize) -> Option { + if k > n || n == 0 { + return Some(0); + } + let k = k.min(n - k); let ks = 1..=k; let ns = (n - k + 1..=n).rev(); ks.zip(ns) .try_fold(1_usize, |acc, (k, n)| Some(acc.checked_mul(n)? / k)) } - let smallest = K.min(max_size - K); - let max_combinations = choose(max_size, smallest); + // sum_i=0..k choose(cursors[i].remaining, k-i) + let max_combinations = self + .cursors + .iter() + .enumerate() + .try_fold(0, |acc, (i, cursor)| { + let n = cursor.max_remaining(self.tables, self.archetypes); + Some(acc + choose(n, K - i)?) + }); let archetype_query = F::IS_ARCHETYPAL && Q::IS_ARCHETYPAL; let known_max = max_combinations.unwrap_or(usize::MAX); @@ -452,11 +443,7 @@ where F: ArchetypeFilter, { fn len(&self) -> usize { - self.query_state - .matched_archetype_ids - .iter() - .map(|id| self.archetypes[*id].len()) - .sum() + self.size_hint().0 } } @@ -476,7 +463,7 @@ struct QueryIterationCursor<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> { // length of the table table or length of the archetype, depending on whether both `Q`'s and `F`'s fetches are dense current_len: usize, // either table row or archetype index, depending on whether both `Q`'s and `F`'s fetches are dense - current_index: usize, + current_row: usize, phantom: PhantomData, } @@ -486,7 +473,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, /// # Safety /// While calling this method on its own cannot cause UB it is marked `unsafe` as the caller must ensure /// that the returned value is not used in any way that would cause two `QueryItem` for the same - /// `archetype_index` or `table_row` to be alive at the same time. + /// `archetype_row` or `table_row` to be alive at the same time. unsafe fn clone_cursor(&self) -> Self { Self { table_id_iter: self.table_id_iter.clone(), @@ -497,7 +484,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, fetch: Q::clone_fetch(&self.fetch), filter: F::clone_fetch(&self.filter), current_len: self.current_len, - current_index: self.current_index, + current_row: self.current_row, phantom: PhantomData, } } @@ -545,7 +532,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, table_id_iter: query_state.matched_table_ids.iter(), archetype_id_iter: query_state.matched_archetype_ids.iter(), current_len: 0, - current_index: 0, + current_row: 0, phantom: PhantomData, } } @@ -553,17 +540,17 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, /// retrieve item returned from most recent `next` call again. #[inline] unsafe fn peek_last(&mut self) -> Option> { - if self.current_index > 0 { - let index = self.current_index - 1; + if self.current_row > 0 { + let index = self.current_row - 1; if Self::IS_DENSE { let entity = self.table_entities.get_unchecked(index); - Some(Q::fetch(&mut self.fetch, *entity, index)) + Some(Q::fetch(&mut self.fetch, *entity, TableRow::new(index))) } else { let archetype_entity = self.archetype_entities.get_unchecked(index); Some(Q::fetch( &mut self.fetch, - archetype_entity.entity, - archetype_entity.table_row, + archetype_entity.entity(), + archetype_entity.table_row(), )) } } else { @@ -571,6 +558,21 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, } } + /// How many values will this cursor return at most? + /// + /// Note that if `Q::IS_ARCHETYPAL && F::IS_ARCHETYPAL`, the return value + /// will be **the exact count of remaining values**. + fn max_remaining(&self, tables: &'w Tables, archetypes: &'w Archetypes) -> usize { + let remaining_matched: usize = if Self::IS_DENSE { + let ids = self.table_id_iter.clone(); + ids.map(|id| tables[*id].entity_count()).sum() + } else { + let ids = self.archetype_id_iter.clone(); + ids.map(|id| archetypes[*id].len()).sum() + }; + remaining_matched + self.current_len - self.current_row + } + // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual /// # Safety @@ -587,7 +589,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, if Self::IS_DENSE { loop { // we are on the beginning of the query, or finished processing a table, so skip to the next - if self.current_index == self.current_len { + if self.current_row == self.current_len { let table_id = self.table_id_iter.next()?; let table = tables.get(*table_id).debug_checked_unwrap(); // SAFETY: `table` is from the world that `fetch/filter` were created for, @@ -596,28 +598,29 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, F::set_table(&mut self.filter, &query_state.filter_state, table); self.table_entities = table.entities(); self.current_len = table.entity_count(); - self.current_index = 0; + self.current_row = 0; continue; } // SAFETY: set_table was called prior. - // `current_index` is a table row in range of the current table, because if it was not, then the if above would have been executed. - let entity = self.table_entities.get_unchecked(self.current_index); - if !F::filter_fetch(&mut self.filter, *entity, self.current_index) { - self.current_index += 1; + // `current_row` is a table row in range of the current table, because if it was not, then the if above would have been executed. + let entity = self.table_entities.get_unchecked(self.current_row); + let row = TableRow::new(self.current_row); + if !F::filter_fetch(&mut self.filter, *entity, row) { + self.current_row += 1; continue; } // SAFETY: set_table was called prior. - // `current_index` is a table row in range of the current table, because if it was not, then the if above would have been executed. - let item = Q::fetch(&mut self.fetch, *entity, self.current_index); + // `current_row` is a table row in range of the current table, because if it was not, then the if above would have been executed. + let item = Q::fetch(&mut self.fetch, *entity, row); - self.current_index += 1; + self.current_row += 1; return Some(item); } } else { loop { - if self.current_index == self.current_len { + if self.current_row == self.current_len { let archetype_id = self.archetype_id_iter.next()?; let archetype = archetypes.get(*archetype_id).debug_checked_unwrap(); // SAFETY: `archetype` and `tables` are from the world that `fetch/filter` were created for, @@ -632,30 +635,30 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, ); self.archetype_entities = archetype.entities(); self.current_len = archetype.len(); - self.current_index = 0; + self.current_row = 0; continue; } // SAFETY: set_archetype was called prior. - // `current_index` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed. - let archetype_entity = self.archetype_entities.get_unchecked(self.current_index); + // `current_row` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed. + let archetype_entity = self.archetype_entities.get_unchecked(self.current_row); if !F::filter_fetch( &mut self.filter, - archetype_entity.entity, - archetype_entity.table_row, + archetype_entity.entity(), + archetype_entity.table_row(), ) { - self.current_index += 1; + self.current_row += 1; continue; } - // SAFETY: set_archetype was called prior, `current_index` is an archetype index in range of the current archetype - // `current_index` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed. + // SAFETY: set_archetype was called prior, `current_row` is an archetype index in range of the current archetype + // `current_row` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed. let item = Q::fetch( &mut self.fetch, - archetype_entity.entity, - archetype_entity.table_row, + archetype_entity.entity(), + archetype_entity.table_row(), ); - self.current_index += 1; + self.current_row += 1; return Some(item); } } diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index ac0767f4c08c3..220754b5e0ff0 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -96,100 +96,6 @@ mod tests { #[test] fn query_filtered_exactsizeiterator_len() { - fn assert_all_sizes_iterator_equal( - iterator: impl ExactSizeIterator, - expected_size: usize, - query_type: &'static str, - ) { - let len = iterator.len(); - let size_hint_0 = iterator.size_hint().0; - let size_hint_1 = iterator.size_hint().1; - // `count` tests that not only it is the expected value, but also - // the value is accurate to what the query returns. - let count = iterator.count(); - // This will show up when one of the asserts in this function fails - println!( - r#"query declared sizes: - for query: {query_type} - expected: {expected_size} - len: {len} - size_hint().0: {size_hint_0} - size_hint().1: {size_hint_1:?} - count(): {count}"# - ); - assert_eq!(len, expected_size); - assert_eq!(size_hint_0, expected_size); - assert_eq!(size_hint_1, Some(expected_size)); - assert_eq!(count, expected_size); - } - fn assert_all_sizes_equal(world: &mut World, expected_size: usize) - where - Q: ReadOnlyWorldQuery, - F: ReadOnlyWorldQuery, - F::ReadOnly: ArchetypeFilter, - { - let mut query = world.query_filtered::(); - let iter = query.iter(world); - let query_type = type_name::>(); - assert_all_sizes_iterator_equal(iter, expected_size, query_type); - } - - let mut world = World::new(); - world.spawn((A(1), B(1))); - world.spawn(A(2)); - world.spawn(A(3)); - - assert_all_sizes_equal::<&A, With>(&mut world, 1); - assert_all_sizes_equal::<&A, Without>(&mut world, 2); - - let mut world = World::new(); - world.spawn((A(1), B(1), C(1))); - world.spawn((A(2), B(2))); - world.spawn((A(3), B(3))); - world.spawn((A(4), C(4))); - world.spawn((A(5), C(5))); - world.spawn((A(6), C(6))); - world.spawn(A(7)); - world.spawn(A(8)); - world.spawn(A(9)); - world.spawn(A(10)); - - // With/Without for B and C - assert_all_sizes_equal::<&A, With>(&mut world, 3); - assert_all_sizes_equal::<&A, With>(&mut world, 4); - assert_all_sizes_equal::<&A, Without>(&mut world, 7); - assert_all_sizes_equal::<&A, Without>(&mut world, 6); - - // With/Without (And) combinations - assert_all_sizes_equal::<&A, (With, With)>(&mut world, 1); - assert_all_sizes_equal::<&A, (With, Without)>(&mut world, 2); - assert_all_sizes_equal::<&A, (Without, With)>(&mut world, 3); - assert_all_sizes_equal::<&A, (Without, Without)>(&mut world, 4); - - // With/Without Or<()> combinations - assert_all_sizes_equal::<&A, Or<(With, With)>>(&mut world, 6); - assert_all_sizes_equal::<&A, Or<(With, Without)>>(&mut world, 7); - assert_all_sizes_equal::<&A, Or<(Without, With)>>(&mut world, 8); - assert_all_sizes_equal::<&A, Or<(Without, Without)>>(&mut world, 9); - assert_all_sizes_equal::<&A, (Or<(With,)>, Or<(With,)>)>(&mut world, 1); - assert_all_sizes_equal::<&A, Or<(Or<(With, With)>, With)>>(&mut world, 6); - - for i in 11..14 { - world.spawn((A(i), D(i))); - } - - assert_all_sizes_equal::<&A, Or<(Or<(With, With)>, With)>>(&mut world, 9); - assert_all_sizes_equal::<&A, Or<(Or<(With, With)>, Without)>>(&mut world, 10); - - // a fair amount of entities - for i in 14..20 { - world.spawn((C(i), D(i))); - } - assert_all_sizes_equal::, With)>(&mut world, 6); - } - - #[test] - fn query_filtered_combination_size() { fn choose(n: usize, k: usize) -> usize { if n == 0 || k == 0 || n < k { return 0; @@ -200,25 +106,30 @@ mod tests { } fn assert_combination(world: &mut World, expected_size: usize) where - Q: WorldQuery, + Q: ReadOnlyWorldQuery, F: ReadOnlyWorldQuery, F::ReadOnly: ArchetypeFilter, { let mut query = world.query_filtered::(); - let iter = query.iter_combinations::(world); let query_type = type_name::>(); - assert_all_sizes_iterator_equal(iter, expected_size, query_type); + let iter = query.iter_combinations::(world); + assert_all_sizes_iterator_equal(iter, expected_size, 0, query_type); + let iter = query.iter_combinations::(world); + assert_all_sizes_iterator_equal(iter, expected_size, 1, query_type); + let iter = query.iter_combinations::(world); + assert_all_sizes_iterator_equal(iter, expected_size, 5, query_type); } fn assert_all_sizes_equal(world: &mut World, expected_size: usize) where - Q: WorldQuery, + Q: ReadOnlyWorldQuery, F: ReadOnlyWorldQuery, F::ReadOnly: ArchetypeFilter, { let mut query = world.query_filtered::(); - let iter = query.iter(world); let query_type = type_name::>(); - assert_all_sizes_iterator_equal(iter, expected_size, query_type); + assert_all_exact_sizes_iterator_equal(query.iter(world), expected_size, 0, query_type); + assert_all_exact_sizes_iterator_equal(query.iter(world), expected_size, 1, query_type); + assert_all_exact_sizes_iterator_equal(query.iter(world), expected_size, 5, query_type); let expected = expected_size; assert_combination::(world, choose(expected, 0)); @@ -226,13 +137,29 @@ mod tests { assert_combination::(world, choose(expected, 2)); assert_combination::(world, choose(expected, 5)); assert_combination::(world, choose(expected, 43)); - assert_combination::(world, choose(expected, 128)); + assert_combination::(world, choose(expected, 64)); + } + fn assert_all_exact_sizes_iterator_equal( + iterator: impl ExactSizeIterator, + expected_size: usize, + skip: usize, + query_type: &'static str, + ) { + let len = iterator.len(); + println!("len: {len}"); + assert_all_sizes_iterator_equal(iterator, expected_size, skip, query_type); + assert_eq!(len, expected_size); } fn assert_all_sizes_iterator_equal( - iterator: impl Iterator, + mut iterator: impl Iterator, expected_size: usize, + skip: usize, query_type: &'static str, ) { + let expected_size = expected_size.saturating_sub(skip); + for _ in 0..skip { + iterator.next(); + } let size_hint_0 = iterator.size_hint().0; let size_hint_1 = iterator.size_hint().1; // `count` tests that not only it is the expected value, but also @@ -240,12 +167,12 @@ mod tests { let count = iterator.count(); // This will show up when one of the asserts in this function fails println!( - r#"query declared sizes: - for query: {query_type} - expected: {expected_size} - size_hint().0: {size_hint_0} - size_hint().1: {size_hint_1:?} - count(): {count}"# + "query declared sizes: \n\ + for query: {query_type} \n\ + expected: {expected_size} \n\ + size_hint().0: {size_hint_0} \n\ + size_hint().1: {size_hint_1:?} \n\ + count(): {count}" ); assert_eq!(size_hint_0, expected_size); assert_eq!(size_hint_1, Some(expected_size)); diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 245ff7dacb7b4..c96dbca3938f8 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -6,7 +6,7 @@ use crate::{ query::{ Access, DebugCheckedUnwrap, FilteredAccess, QueryCombinationIter, QueryIter, WorldQuery, }, - storage::TableId, + storage::{TableId, TableRow}, world::{World, WorldId}, }; use bevy_tasks::ComputeTaskPool; @@ -276,15 +276,9 @@ impl QueryState { entity: Entity, ) -> Result, QueryEntityError> { self.update_archetypes(world); + let change_tick = world.change_tick(); // SAFETY: query has unique world access - unsafe { - self.get_unchecked_manual( - world, - entity, - world.last_change_tick(), - world.read_change_tick(), - ) - } + unsafe { self.get_unchecked_manual(world, entity, world.last_change_tick(), change_tick) } } /// Returns the query results for the given array of [`Entity`]. @@ -333,15 +327,11 @@ impl QueryState { ) -> Result<[Q::Item<'w>; N], QueryEntityError> { self.update_archetypes(world); + let change_tick = world.change_tick(); // SAFETY: method requires exclusive world access // and world has been validated via update_archetypes unsafe { - self.get_many_unchecked_manual( - world, - entities, - world.last_change_tick(), - world.read_change_tick(), - ) + self.get_many_unchecked_manual(world, entities, world.last_change_tick(), change_tick) } } @@ -418,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.index); 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)) } @@ -525,10 +514,11 @@ impl QueryState { /// Returns an [`Iterator`] over the query results for the given [`World`]. #[inline] pub fn iter_mut<'w, 's>(&'s mut self, world: &'w mut World) -> QueryIter<'w, 's, Q, F> { + let change_tick = world.change_tick(); // SAFETY: query has unique world access unsafe { self.update_archetypes(world); - self.iter_unchecked_manual(world, world.last_change_tick(), world.read_change_tick()) + self.iter_unchecked_manual(world, world.last_change_tick(), change_tick) } } @@ -611,14 +601,11 @@ impl QueryState { &'s mut self, world: &'w mut World, ) -> QueryCombinationIter<'w, 's, Q, F, K> { + let change_tick = world.change_tick(); // SAFETY: query has unique world access unsafe { self.update_archetypes(world); - self.iter_combinations_unchecked_manual( - world, - world.last_change_tick(), - world.read_change_tick(), - ) + self.iter_combinations_unchecked_manual(world, world.last_change_tick(), change_tick) } } @@ -665,14 +652,10 @@ impl QueryState { EntityList::Item: Borrow, { self.update_archetypes(world); + let change_tick = world.change_tick(); // SAFETY: Query has unique world access. unsafe { - self.iter_many_unchecked_manual( - entities, - world, - world.last_change_tick(), - world.read_change_tick(), - ) + self.iter_many_unchecked_manual(entities, world, world.last_change_tick(), change_tick) } } @@ -797,15 +780,11 @@ impl QueryState { /// `iter_mut()` method, but cannot be chained like a normal [`Iterator`]. #[inline] pub fn for_each_mut<'w, FN: FnMut(Q::Item<'w>)>(&mut self, world: &'w mut World, func: FN) { + let change_tick = world.change_tick(); // SAFETY: query has unique world access unsafe { self.update_archetypes(world); - self.for_each_unchecked_manual( - world, - func, - world.last_change_tick(), - world.read_change_tick(), - ); + self.for_each_unchecked_manual(world, func, world.last_change_tick(), change_tick); } } @@ -873,6 +852,7 @@ impl QueryState { batch_size: usize, func: FN, ) { + let change_tick = world.change_tick(); // SAFETY: query has unique world access unsafe { self.update_archetypes(world); @@ -881,7 +861,7 @@ impl QueryState { batch_size, func, world.last_change_tick(), - world.read_change_tick(), + change_tick, ); } } @@ -947,6 +927,7 @@ impl QueryState { let entities = table.entities(); for row in 0..table.entity_count() { let entity = entities.get_unchecked(row); + let row = TableRow::new(row); if !F::filter_fetch(&mut filter, *entity, row) { continue; } @@ -966,15 +947,15 @@ impl QueryState { let archetype_entity = entities.get_unchecked(idx); if !F::filter_fetch( &mut filter, - archetype_entity.entity, - archetype_entity.table_row, + archetype_entity.entity(), + archetype_entity.table_row(), ) { continue; } func(Q::fetch( &mut fetch, - archetype_entity.entity, - archetype_entity.table_row, + archetype_entity.entity(), + archetype_entity.table_row(), )); } } @@ -1041,6 +1022,7 @@ impl QueryState { F::set_table(&mut filter, &self.filter_state, table); for row in offset..offset + len { let entity = entities.get_unchecked(row); + let row = TableRow::new(row); if !F::filter_fetch(&mut filter, *entity, row) { continue; } @@ -1093,19 +1075,19 @@ impl QueryState { F::set_archetype(&mut filter, &self.filter_state, archetype, table); let entities = archetype.entities(); - for archetype_index in offset..offset + len { - let archetype_entity = entities.get_unchecked(archetype_index); + for archetype_row in offset..offset + len { + let archetype_entity = entities.get_unchecked(archetype_row); if !F::filter_fetch( &mut filter, - archetype_entity.entity, - archetype_entity.table_row, + archetype_entity.entity(), + archetype_entity.table_row(), ) { continue; } func(Q::fetch( &mut fetch, - archetype_entity.entity, - archetype_entity.table_row, + archetype_entity.entity(), + archetype_entity.table_row(), )); } }; @@ -1195,14 +1177,9 @@ impl QueryState { ) -> Result, QuerySingleError> { self.update_archetypes(world); + let change_tick = world.change_tick(); // SAFETY: query has unique world access - unsafe { - self.get_single_unchecked_manual( - world, - world.last_change_tick(), - world.read_change_tick(), - ) - } + unsafe { self.get_single_unchecked_manual(world, world.last_change_tick(), change_tick) } } /// Returns a query result when there is exactly one entity matching the query. @@ -1293,7 +1270,7 @@ mod tests { // These don't matter for the test let last_change_tick = world.last_change_tick(); - let change_tick = world.read_change_tick(); + let change_tick = world.change_tick(); // It's best to test get_many_unchecked_manual directly, // as it is shared and unsafe diff --git a/crates/bevy_ecs/src/schedule/stage.rs b/crates/bevy_ecs/src/schedule/stage.rs index 238743965d7ef..d7386d2e5990d 100644 --- a/crates/bevy_ecs/src/schedule/stage.rs +++ b/crates/bevy_ecs/src/schedule/stage.rs @@ -229,12 +229,10 @@ impl SystemStage { } pub fn apply_buffers(&mut self, world: &mut World) { + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!("stage::apply_buffers").entered(); for container in &mut self.parallel { - let system = container.system_mut(); - #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!("system_commands", name = &*system.name()) - .entered(); - system.apply_buffers(world); + container.system_mut().apply_buffers(world); } } @@ -781,15 +779,7 @@ impl Stage for SystemStage { .entered(); container.system_mut().run((), world); } - { - #[cfg(feature = "trace")] - let _system_span = bevy_utils::tracing::info_span!( - "system_commands", - name = &*container.name() - ) - .entered(); - container.system_mut().apply_buffers(world); - } + container.system_mut().apply_buffers(world); } } @@ -813,15 +803,7 @@ impl Stage for SystemStage { .entered(); container.system_mut().run((), world); } - { - #[cfg(feature = "trace")] - let _system_span = bevy_utils::tracing::info_span!( - "system_commands", - name = &*container.name() - ) - .entered(); - container.system_mut().apply_buffers(world); - } + container.system_mut().apply_buffers(world); } } @@ -829,12 +811,6 @@ impl Stage for SystemStage { if self.apply_buffers { for container in &mut self.parallel { if container.should_run { - #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!( - "system_commands", - name = &*container.name() - ) - .entered(); container.system_mut().apply_buffers(world); } } @@ -852,15 +828,7 @@ impl Stage for SystemStage { .entered(); container.system_mut().run((), world); } - { - #[cfg(feature = "trace")] - let _system_span = bevy_utils::tracing::info_span!( - "system_commands", - name = &*container.name() - ) - .entered(); - container.system_mut().apply_buffers(world); - } + container.system_mut().apply_buffers(world); } } diff --git a/crates/bevy_ecs/src/storage/blob_vec.rs b/crates/bevy_ecs/src/storage/blob_vec.rs index 8a223125d15e6..c0bfa3302572c 100644 --- a/crates/bevy_ecs/src/storage/blob_vec.rs +++ b/crates/bevy_ecs/src/storage/blob_vec.rs @@ -17,8 +17,6 @@ pub(super) struct BlobVec { len: usize, // the `data` ptr's layout is always `array_layout(item_layout, capacity)` data: NonNull, - // the `swap_scratch` ptr's layout is always `item_layout` - swap_scratch: NonNull, // None if the underlying type doesn't need to be dropped drop: Option)>, } @@ -31,7 +29,6 @@ impl std::fmt::Debug for BlobVec { .field("capacity", &self.capacity) .field("len", &self.len) .field("data", &self.data) - .field("swap_scratch", &self.swap_scratch) .finish() } } @@ -52,7 +49,6 @@ impl BlobVec { if item_layout.size() == 0 { let align = NonZeroUsize::new(item_layout.align()).expect("alignment must be > 0"); BlobVec { - swap_scratch: NonNull::dangling(), data: bevy_ptr::dangling_with_align(align), capacity: usize::MAX, len: 0, @@ -60,10 +56,7 @@ impl BlobVec { drop, } } else { - let swap_scratch = NonNull::new(std::alloc::alloc(item_layout)) - .unwrap_or_else(|| std::alloc::handle_alloc_error(item_layout)); let mut blob_vec = BlobVec { - swap_scratch, data: NonNull::dangling(), capacity: 0, len: 0, @@ -213,28 +206,23 @@ impl BlobVec { /// caller's responsibility to drop the returned pointer, if that is desirable. /// /// # Safety - /// It is the caller's responsibility to ensure that `index` is < `self.len()` + /// It is the caller's responsibility to ensure that `index` is less than `self.len()`. #[inline] #[must_use = "The returned pointer should be used to dropped the removed element"] pub unsafe fn swap_remove_and_forget_unchecked(&mut self, index: usize) -> OwningPtr<'_> { - // FIXME: This should probably just use `core::ptr::swap` and return an `OwningPtr` - // into the underlying `BlobVec` allocation, and remove swap_scratch - debug_assert!(index < self.len()); - let last = self.len - 1; - let swap_scratch = self.swap_scratch.as_ptr(); - std::ptr::copy_nonoverlapping::( - self.get_unchecked_mut(index).as_ptr(), - swap_scratch, - self.item_layout.size(), - ); - std::ptr::copy::( - self.get_unchecked_mut(last).as_ptr(), - self.get_unchecked_mut(index).as_ptr(), - self.item_layout.size(), - ); - self.len -= 1; - OwningPtr::new(self.swap_scratch) + let new_len = self.len - 1; + let size = self.item_layout.size(); + if index != new_len { + std::ptr::swap_nonoverlapping::( + self.get_unchecked_mut(index).as_ptr(), + self.get_unchecked_mut(new_len).as_ptr(), + size, + ); + } + self.len = new_len; + // Cannot use get_unchecked here as this is technically out of bounds after changing len. + self.get_ptr_mut().byte_add(new_len * size).promote() } /// Removes the value at `index` and copies the value stored into `ptr`. @@ -333,12 +321,6 @@ impl BlobVec { impl Drop for BlobVec { fn drop(&mut self) { self.clear(); - if self.item_layout.size() > 0 { - // SAFETY: the `swap_scratch` pointer is always allocated using `self.item_layout` - unsafe { - std::alloc::dealloc(self.swap_scratch.as_ptr(), self.item_layout); - } - } let array_layout = array_layout(&self.item_layout, self.capacity).expect("array layout should be valid"); if array_layout.size() > 0 { diff --git a/crates/bevy_ecs/src/storage/resource.rs b/crates/bevy_ecs/src/storage/resource.rs index e047fb3c96bf8..01fa742e06362 100644 --- a/crates/bevy_ecs/src/storage/resource.rs +++ b/crates/bevy_ecs/src/storage/resource.rs @@ -1,8 +1,7 @@ use crate::archetype::ArchetypeComponentId; -use crate::component::{ComponentId, ComponentTicks, Components}; -use crate::storage::{Column, SparseSet}; +use crate::component::{ComponentId, ComponentTicks, Components, TickCells}; +use crate::storage::{Column, SparseSet, TableRow}; use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; -use std::cell::UnsafeCell; /// The type-erased backing storage and metadata for a single resource within a [`World`]. /// @@ -13,6 +12,9 @@ pub struct ResourceData { } impl ResourceData { + /// The only row in the underlying column. + const ROW: TableRow = TableRow::new(0); + /// Returns true if the resource is populated. #[inline] pub fn is_present(&self) -> bool { @@ -28,24 +30,18 @@ impl ResourceData { /// Gets a read-only pointer to the underlying resource, if available. #[inline] pub fn get_data(&self) -> Option> { - self.column.get_data(0) + self.column.get_data(Self::ROW) } /// Gets a read-only reference to the change ticks of the underlying resource, if available. #[inline] - pub fn get_ticks(&self) -> Option<&ComponentTicks> { - self.column - .get_ticks(0) - // SAFETY: - // - This borrow's lifetime is bounded by the lifetime on self. - // - A read-only borrow on self can only exist while a mutable borrow doesn't - // exist. - .map(|ticks| unsafe { ticks.deref() }) + pub fn get_ticks(&self) -> Option { + self.column.get_ticks(Self::ROW) } #[inline] - pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, &UnsafeCell)> { - self.column.get(0) + pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, TickCells<'_>)> { + self.column.get(Self::ROW) } /// Inserts a value into the resource. If a value is already present @@ -61,7 +57,7 @@ impl ResourceData { #[inline] pub(crate) unsafe fn insert(&mut self, value: OwningPtr<'_>, change_tick: u32) { if self.is_present() { - self.column.replace(0, value, change_tick); + self.column.replace(Self::ROW, value, change_tick); } else { self.column.push(value, ComponentTicks::new(change_tick)); } @@ -84,8 +80,12 @@ impl ResourceData { change_ticks: ComponentTicks, ) { if self.is_present() { - self.column.replace_untracked(0, value); - *self.column.get_ticks_unchecked(0).deref_mut() = change_ticks; + self.column.replace_untracked(Self::ROW, value); + *self.column.get_added_ticks_unchecked(Self::ROW).deref_mut() = change_ticks.added; + *self + .column + .get_changed_ticks_unchecked(Self::ROW) + .deref_mut() = change_ticks.changed; } else { self.column.push(value, change_ticks); } @@ -103,7 +103,7 @@ impl ResourceData { #[inline] #[must_use = "The returned pointer to the removed component should be used or dropped"] pub(crate) unsafe fn remove(&mut self) -> Option<(OwningPtr<'_>, ComponentTicks)> { - self.column.swap_remove_and_forget(0) + self.column.swap_remove_and_forget(Self::ROW) } /// Removes a value from the resource, if present, and drops it. diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index fdb9a21176a5f..cc3da14682cb9 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -1,12 +1,12 @@ use crate::{ - component::{ComponentId, ComponentInfo, ComponentTicks}, + component::{ComponentId, ComponentInfo, ComponentTicks, Tick, TickCells}, entity::Entity, - storage::Column, + storage::{Column, TableRow}, }; use bevy_ptr::{OwningPtr, Ptr}; use std::{cell::UnsafeCell, hash::Hash, marker::PhantomData}; -type EntityId = u32; +type EntityIndex = u32; #[derive(Debug)] pub(crate) struct SparseArray { @@ -14,6 +14,14 @@ pub(crate) struct SparseArray { marker: PhantomData, } +/// A space-optimized version of [`SparseArray`] that cannot be changed +/// after construction. +#[derive(Debug)] +pub(crate) struct ImmutableSparseArray { + values: Box<[Option]>, + marker: PhantomData, +} + impl Default for SparseArray { fn default() -> Self { Self::new() @@ -30,6 +38,27 @@ impl SparseArray { } } +macro_rules! impl_sparse_array { + ($ty:ident) => { + impl $ty { + #[inline] + pub fn contains(&self, index: I) -> bool { + let index = index.sparse_set_index(); + self.values.get(index).map(|v| v.is_some()).unwrap_or(false) + } + + #[inline] + pub fn get(&self, index: I) -> Option<&V> { + let index = index.sparse_set_index(); + self.values.get(index).map(|v| v.as_ref()).unwrap_or(None) + } + } + }; +} + +impl_sparse_array!(SparseArray); +impl_sparse_array!(ImmutableSparseArray); + impl SparseArray { #[inline] pub fn insert(&mut self, index: I, value: V) { @@ -40,18 +69,6 @@ impl SparseArray { self.values[index] = Some(value); } - #[inline] - pub fn contains(&self, index: I) -> bool { - let index = index.sparse_set_index(); - self.values.get(index).map(|v| v.is_some()).unwrap_or(false) - } - - #[inline] - pub fn get(&self, index: I) -> Option<&V> { - let index = index.sparse_set_index(); - self.values.get(index).map(|v| v.as_ref()).unwrap_or(None) - } - #[inline] pub fn get_mut(&mut self, index: I) -> Option<&mut V> { let index = index.sparse_set_index(); @@ -70,6 +87,13 @@ impl SparseArray { pub fn clear(&mut self) { self.values.clear(); } + + pub(crate) fn into_immutable(self) -> ImmutableSparseArray { + ImmutableSparseArray { + values: self.values.into_boxed_slice(), + marker: PhantomData, + } + } } /// A sparse data structure of [Components](crate::component::Component) @@ -78,14 +102,14 @@ impl SparseArray { #[derive(Debug)] pub struct ComponentSparseSet { dense: Column, - // Internally this only relies on the Entity ID to keep track of where the component data is + // Internally this only relies on the Entity index to keep track of where the component data is // stored for entities that are alive. The generation is not required, but is stored // in debug builds to validate that access is correct. #[cfg(not(debug_assertions))] - entities: Vec, + entities: Vec, #[cfg(debug_assertions)] entities: Vec, - sparse: SparseArray, + sparse: SparseArray, } impl ComponentSparseSet { @@ -123,7 +147,8 @@ impl ComponentSparseSet { if let Some(&dense_index) = self.sparse.get(entity.index()) { #[cfg(debug_assertions)] assert_eq!(entity, self.entities[dense_index as usize]); - self.dense.replace(dense_index as usize, value, change_tick); + self.dense + .replace(TableRow::new(dense_index as usize), value, change_tick); } else { let dense_index = self.dense.len(); self.dense.push(value, ComponentTicks::new(change_tick)); @@ -156,35 +181,66 @@ impl ComponentSparseSet { #[inline] pub fn get(&self, entity: Entity) -> Option> { self.sparse.get(entity.index()).map(|dense_index| { - let dense_index = *dense_index as usize; + let dense_index = (*dense_index) as usize; #[cfg(debug_assertions)] assert_eq!(entity, self.entities[dense_index]); // SAFETY: if the sparse index points to something in the dense vec, it exists - unsafe { self.dense.get_data_unchecked(dense_index) } + unsafe { self.dense.get_data_unchecked(TableRow::new(dense_index)) } }) } #[inline] - pub fn get_with_ticks(&self, entity: Entity) -> Option<(Ptr<'_>, &UnsafeCell)> { - let dense_index = *self.sparse.get(entity.index())? as usize; + pub fn get_with_ticks(&self, entity: Entity) -> Option<(Ptr<'_>, TickCells<'_>)> { + let dense_index = TableRow::new(*self.sparse.get(entity.index())? as usize); #[cfg(debug_assertions)] - assert_eq!(entity, self.entities[dense_index]); + assert_eq!(entity, self.entities[dense_index.index()]); // SAFETY: if the sparse index points to something in the dense vec, it exists unsafe { Some(( self.dense.get_data_unchecked(dense_index), - self.dense.get_ticks_unchecked(dense_index), + TickCells { + added: self.dense.get_added_ticks_unchecked(dense_index), + changed: self.dense.get_changed_ticks_unchecked(dense_index), + }, )) } } #[inline] - pub fn get_ticks(&self, entity: Entity) -> Option<&UnsafeCell> { + pub fn get_added_ticks(&self, entity: Entity) -> Option<&UnsafeCell> { + let dense_index = *self.sparse.get(entity.index())? as usize; + #[cfg(debug_assertions)] + assert_eq!(entity, self.entities[dense_index]); + // SAFETY: if the sparse index points to something in the dense vec, it exists + unsafe { + Some( + self.dense + .get_added_ticks_unchecked(TableRow::new(dense_index)), + ) + } + } + + #[inline] + pub fn get_changed_ticks(&self, entity: Entity) -> Option<&UnsafeCell> { + let dense_index = *self.sparse.get(entity.index())? as usize; + #[cfg(debug_assertions)] + assert_eq!(entity, self.entities[dense_index]); + // SAFETY: if the sparse index points to something in the dense vec, it exists + unsafe { + Some( + self.dense + .get_changed_ticks_unchecked(TableRow::new(dense_index)), + ) + } + } + + #[inline] + pub fn get_ticks(&self, entity: Entity) -> Option { let dense_index = *self.sparse.get(entity.index())? as usize; #[cfg(debug_assertions)] assert_eq!(entity, self.entities[dense_index]); // SAFETY: if the sparse index points to something in the dense vec, it exists - unsafe { Some(self.dense.get_ticks_unchecked(dense_index)) } + unsafe { Some(self.dense.get_ticks_unchecked(TableRow::new(dense_index))) } } /// Removes the `entity` from this sparse set and returns a pointer to the associated value (if @@ -198,7 +254,10 @@ impl ComponentSparseSet { self.entities.swap_remove(dense_index); let is_last = dense_index == self.dense.len() - 1; // SAFETY: dense_index was just removed from `sparse`, which ensures that it is valid - let (value, _) = unsafe { self.dense.swap_remove_and_forget_unchecked(dense_index) }; + let (value, _) = unsafe { + self.dense + .swap_remove_and_forget_unchecked(TableRow::new(dense_index)) + }; if !is_last { let swapped_entity = self.entities[dense_index]; #[cfg(not(debug_assertions))] @@ -219,7 +278,7 @@ impl ComponentSparseSet { self.entities.swap_remove(dense_index); let is_last = dense_index == self.dense.len() - 1; // SAFETY: if the sparse index points to something in the dense vec, it exists - unsafe { self.dense.swap_remove_unchecked(dense_index) } + unsafe { self.dense.swap_remove_unchecked(TableRow::new(dense_index)) } if !is_last { let swapped_entity = self.entities[dense_index]; #[cfg(not(debug_assertions))] @@ -249,11 +308,75 @@ pub struct SparseSet { sparse: SparseArray, } +/// A space-optimized version of [`SparseSet`] that cannot be changed +/// after construction. +#[derive(Debug)] +pub(crate) struct ImmutableSparseSet { + dense: Box<[V]>, + indices: Box<[I]>, + sparse: ImmutableSparseArray, +} + +macro_rules! impl_sparse_set { + ($ty:ident) => { + impl $ty { + #[inline] + pub fn len(&self) -> usize { + self.dense.len() + } + + #[inline] + pub fn contains(&self, index: I) -> bool { + self.sparse.contains(index) + } + + pub fn get(&self, index: I) -> Option<&V> { + self.sparse.get(index).map(|dense_index| { + // SAFETY: if the sparse index points to something in the dense vec, it exists + unsafe { self.dense.get_unchecked(*dense_index) } + }) + } + + pub fn get_mut(&mut self, index: I) -> Option<&mut V> { + let dense = &mut self.dense; + self.sparse.get(index).map(move |dense_index| { + // SAFETY: if the sparse index points to something in the dense vec, it exists + unsafe { dense.get_unchecked_mut(*dense_index) } + }) + } + + pub fn indices(&self) -> impl Iterator + '_ { + self.indices.iter().cloned() + } + + pub fn values(&self) -> impl Iterator { + self.dense.iter() + } + + pub fn values_mut(&mut self) -> impl Iterator { + self.dense.iter_mut() + } + + pub fn iter(&self) -> impl Iterator { + self.indices.iter().zip(self.dense.iter()) + } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.indices.iter().zip(self.dense.iter_mut()) + } + } + }; +} + +impl_sparse_set!(SparseSet); +impl_sparse_set!(ImmutableSparseSet); + impl Default for SparseSet { fn default() -> Self { Self::new() } } + impl SparseSet { pub const fn new() -> Self { Self { @@ -306,36 +429,11 @@ impl SparseSet { } } - #[inline] - pub fn len(&self) -> usize { - self.dense.len() - } - #[inline] pub fn is_empty(&self) -> bool { self.dense.len() == 0 } - #[inline] - pub fn contains(&self, index: I) -> bool { - self.sparse.contains(index) - } - - pub fn get(&self, index: I) -> Option<&V> { - self.sparse.get(index).map(|dense_index| { - // SAFETY: if the sparse index points to something in the dense vec, it exists - unsafe { self.dense.get_unchecked(*dense_index) } - }) - } - - pub fn get_mut(&mut self, index: I) -> Option<&mut V> { - let dense = &mut self.dense; - self.sparse.get(index).map(move |dense_index| { - // SAFETY: if the sparse index points to something in the dense vec, it exists - unsafe { dense.get_unchecked_mut(*dense_index) } - }) - } - pub fn remove(&mut self, index: I) -> Option { self.sparse.remove(index).map(|dense_index| { let is_last = dense_index == self.dense.len() - 1; @@ -349,27 +447,20 @@ impl SparseSet { }) } - pub fn indices(&self) -> impl Iterator + '_ { - self.indices.iter().cloned() - } - - pub fn values(&self) -> impl Iterator { - self.dense.iter() - } - - pub fn values_mut(&mut self) -> impl Iterator { - self.dense.iter_mut() - } - - pub fn iter(&self) -> impl Iterator { - self.indices.iter().zip(self.dense.iter()) - } - - pub fn iter_mut(&mut self) -> impl Iterator { - self.indices.iter().zip(self.dense.iter_mut()) + pub(crate) fn into_immutable(self) -> ImmutableSparseSet { + ImmutableSparseSet { + dense: self.dense.into_boxed_slice(), + indices: self.indices.into_boxed_slice(), + sparse: self.sparse.into_immutable(), + } } } +/// Represents something that can be stored in a [`SparseSet`] as an integer. +/// +/// Ideally, the `usize` values should be very small (ie: incremented starting from +/// zero), as the number of bits needed to represent a `SparseSetIndex` in a `FixedBitSet` +/// is proportional to the **value** of those `usize`. pub trait SparseSetIndex: Clone + PartialEq + Eq + Hash { fn sparse_set_index(&self) -> usize; fn get_sparse_set_index(value: usize) -> Self; diff --git a/crates/bevy_ecs/src/storage/table.rs b/crates/bevy_ecs/src/storage/table.rs index adb684469a6d6..e5dc58dc26d5a 100644 --- a/crates/bevy_ecs/src/storage/table.rs +++ b/crates/bevy_ecs/src/storage/table.rs @@ -1,10 +1,10 @@ use crate::{ - component::{ComponentId, ComponentInfo, ComponentTicks, Components}, + component::{ComponentId, ComponentInfo, ComponentTicks, Components, Tick, TickCells}, entity::Entity, query::DebugCheckedUnwrap, - storage::{blob_vec::BlobVec, SparseSet}, + storage::{blob_vec::BlobVec, ImmutableSparseSet, SparseSet}, }; -use bevy_ptr::{OwningPtr, Ptr, PtrMut}; +use bevy_ptr::{OwningPtr, Ptr, PtrMut, UnsafeCellDeref}; use bevy_utils::HashMap; use std::alloc::Layout; use std::{ @@ -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] @@ -32,10 +47,46 @@ impl TableId { } } +/// A opaque newtype for rows in [`Table`]s. Specifies a single row in a specific table. +/// +/// Values of this type are retreivable from [`Archetype::entity_table_row`] and can be +/// used alongside [`Archetype::table_id`] to fetch the exact table and row where an +/// [`Entity`]'s +/// +/// Values of this type are only valid so long as entities have not moved around. +/// Adding and removing components from an entity, or despawning it will invalidate +/// potentially any table row in the table the entity was previously stored in. Users +/// should *always* fetch the approripate row from the entity's [`Archetype`] before +/// fetching the entity's components. +/// +/// [`Archetype`]: crate::archetype::Archetype +/// [`Archetype::entity_table_row`]: crate::archetype::Archetype::entity_table_row +/// [`Archetype::table_id`]: crate::archetype::Archetype::table_id +/// [`Entity`]: crate::entity::Entity +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +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 as u32) + } + + /// Gets the index of the row. + #[inline] + pub const fn index(self) -> usize { + self.0 as usize + } +} + #[derive(Debug)] pub struct Column { data: BlobVec, - ticks: Vec>, + added_ticks: Vec>, + changed_ticks: Vec>, } impl Column { @@ -44,7 +95,8 @@ impl Column { Column { // SAFETY: component_info.drop() is valid for the types that will be inserted. data: unsafe { BlobVec::new(component_info.layout(), component_info.drop(), capacity) }, - ticks: Vec::with_capacity(capacity), + added_ticks: Vec::with_capacity(capacity), + changed_ticks: Vec::with_capacity(capacity), } } @@ -60,15 +112,11 @@ impl Column { /// # Safety /// Assumes data has already been allocated for the given row. #[inline] - pub(crate) unsafe fn initialize( - &mut self, - row: usize, - data: OwningPtr<'_>, - ticks: ComponentTicks, - ) { - debug_assert!(row < self.len()); - self.data.initialize_unchecked(row, data); - *self.ticks.get_unchecked_mut(row).get_mut() = ticks; + pub(crate) unsafe fn initialize(&mut self, row: TableRow, data: OwningPtr<'_>, tick: Tick) { + debug_assert!(row.index() < self.len()); + self.data.initialize_unchecked(row.index(), data); + *self.added_ticks.get_unchecked_mut(row.index()).get_mut() = tick; + *self.changed_ticks.get_unchecked_mut(row.index()).get_mut() = tick; } /// Writes component data to the column at given row. @@ -77,11 +125,11 @@ impl Column { /// # Safety /// Assumes data has already been allocated for the given row. #[inline] - pub(crate) unsafe fn replace(&mut self, row: usize, data: OwningPtr<'_>, change_tick: u32) { - debug_assert!(row < self.len()); - self.data.replace_unchecked(row, data); - self.ticks - .get_unchecked_mut(row) + pub(crate) unsafe fn replace(&mut self, row: TableRow, data: OwningPtr<'_>, change_tick: u32) { + debug_assert!(row.index() < self.len()); + self.data.replace_unchecked(row.index(), data); + self.changed_ticks + .get_unchecked_mut(row.index()) .get_mut() .set_changed(change_tick); } @@ -93,9 +141,9 @@ impl Column { /// # Safety /// Assumes data has already been allocated for the given row. #[inline] - pub(crate) unsafe fn replace_untracked(&mut self, row: usize, data: OwningPtr<'_>) { - debug_assert!(row < self.len()); - self.data.replace_unchecked(row, data); + pub(crate) unsafe fn replace_untracked(&mut self, row: TableRow, data: OwningPtr<'_>) { + debug_assert!(row.index() < self.len()); + self.data.replace_unchecked(row.index(), data); } #[inline] @@ -111,22 +159,24 @@ impl Column { /// # Safety /// index must be in-bounds #[inline] - pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: usize) { - self.data.swap_remove_and_drop_unchecked(row); - self.ticks.swap_remove(row); + pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: TableRow) { + self.data.swap_remove_and_drop_unchecked(row.index()); + self.added_ticks.swap_remove(row.index()); + self.changed_ticks.swap_remove(row.index()); } #[inline] #[must_use = "The returned pointer should be used to drop the removed component"] pub(crate) fn swap_remove_and_forget( &mut self, - row: usize, + row: TableRow, ) -> Option<(OwningPtr<'_>, ComponentTicks)> { - (row < self.data.len()).then(|| { + (row.index() < self.data.len()).then(|| { // SAFETY: The row was length checked before this. - let data = unsafe { self.data.swap_remove_and_forget_unchecked(row) }; - let ticks = self.ticks.swap_remove(row).into_inner(); - (data, ticks) + let data = unsafe { self.data.swap_remove_and_forget_unchecked(row.index()) }; + let added = self.added_ticks.swap_remove(row.index()).into_inner(); + let changed = self.changed_ticks.swap_remove(row.index()).into_inner(); + (data, ComponentTicks { added, changed }) }) } @@ -136,11 +186,12 @@ impl Column { #[must_use = "The returned pointer should be used to dropped the removed component"] pub(crate) unsafe fn swap_remove_and_forget_unchecked( &mut self, - row: usize, + row: TableRow, ) -> (OwningPtr<'_>, ComponentTicks) { - let data = self.data.swap_remove_and_forget_unchecked(row); - let ticks = self.ticks.swap_remove(row).into_inner(); - (data, ticks) + let data = self.data.swap_remove_and_forget_unchecked(row.index()); + let added = self.added_ticks.swap_remove(row.index()).into_inner(); + let changed = self.changed_ticks.swap_remove(row.index()).into_inner(); + (data, ComponentTicks { added, changed }) } /// Removes the element from `other` at `src_row` and inserts it @@ -158,26 +209,31 @@ impl Column { pub(crate) unsafe fn initialize_from_unchecked( &mut self, other: &mut Column, - src_row: usize, - dst_row: usize, + src_row: TableRow, + dst_row: TableRow, ) { debug_assert!(self.data.layout() == other.data.layout()); - let ptr = self.data.get_unchecked_mut(dst_row); - other.data.swap_remove_unchecked(src_row, ptr); - *self.ticks.get_unchecked_mut(dst_row) = other.ticks.swap_remove(src_row); + let ptr = self.data.get_unchecked_mut(dst_row.index()); + other.data.swap_remove_unchecked(src_row.index(), ptr); + *self.added_ticks.get_unchecked_mut(dst_row.index()) = + other.added_ticks.swap_remove(src_row.index()); + *self.changed_ticks.get_unchecked_mut(dst_row.index()) = + other.changed_ticks.swap_remove(src_row.index()); } // # Safety // - ptr must point to valid data of this column's component type pub(crate) unsafe fn push(&mut self, ptr: OwningPtr<'_>, ticks: ComponentTicks) { self.data.push(ptr); - self.ticks.push(UnsafeCell::new(ticks)); + self.added_ticks.push(UnsafeCell::new(ticks.added)); + self.changed_ticks.push(UnsafeCell::new(ticks.changed)); } #[inline] pub(crate) fn reserve_exact(&mut self, additional: usize) { self.data.reserve_exact(additional); - self.ticks.reserve_exact(additional); + self.added_ticks.reserve_exact(additional); + self.changed_ticks.reserve_exact(additional); } #[inline] @@ -192,116 +248,205 @@ impl Column { } #[inline] - pub fn get_ticks_slice(&self) -> &[UnsafeCell] { - &self.ticks + pub fn get_added_ticks_slice(&self) -> &[UnsafeCell] { + &self.added_ticks } #[inline] - pub fn get(&self, row: usize) -> Option<(Ptr<'_>, &UnsafeCell)> { - (row < self.data.len()) + pub fn get_changed_ticks_slice(&self) -> &[UnsafeCell] { + &self.changed_ticks + } + + #[inline] + pub fn get(&self, row: TableRow) -> Option<(Ptr<'_>, TickCells<'_>)> { + (row.index() < self.data.len()) // SAFETY: The row is length checked before fetching the pointer. This is being // accessed through a read-only reference to the column. - .then(|| unsafe { (self.data.get_unchecked(row), self.ticks.get_unchecked(row)) }) + .then(|| unsafe { + ( + self.data.get_unchecked(row.index()), + TickCells { + added: self.added_ticks.get_unchecked(row.index()), + changed: self.changed_ticks.get_unchecked(row.index()), + }, + ) + }) } #[inline] - pub fn get_data(&self, row: usize) -> Option> { + pub fn get_data(&self, row: TableRow) -> Option> { // SAFETY: The row is length checked before fetching the pointer. This is being // accessed through a read-only reference to the column. - (row < self.data.len()).then(|| unsafe { self.data.get_unchecked(row) }) + (row.index() < self.data.len()).then(|| unsafe { self.data.get_unchecked(row.index()) }) } /// # Safety /// - index must be in-bounds /// - no other reference to the data of the same row can exist at the same time #[inline] - pub unsafe fn get_data_unchecked(&self, row: usize) -> Ptr<'_> { - debug_assert!(row < self.data.len()); - self.data.get_unchecked(row) + pub unsafe fn get_data_unchecked(&self, row: TableRow) -> Ptr<'_> { + debug_assert!(row.index() < self.data.len()); + self.data.get_unchecked(row.index()) } #[inline] - pub fn get_data_mut(&mut self, row: usize) -> Option> { + pub fn get_data_mut(&mut self, row: TableRow) -> Option> { // SAFETY: The row is length checked before fetching the pointer. This is being // accessed through an exclusive reference to the column. - (row < self.data.len()).then(|| unsafe { self.data.get_unchecked_mut(row) }) + (row.index() < self.data.len()).then(|| unsafe { self.data.get_unchecked_mut(row.index()) }) } /// # Safety /// - index must be in-bounds /// - no other reference to the data of the same row can exist at the same time #[inline] - pub(crate) unsafe fn get_data_unchecked_mut(&mut self, row: usize) -> PtrMut<'_> { - debug_assert!(row < self.data.len()); - self.data.get_unchecked_mut(row) + pub(crate) unsafe fn get_data_unchecked_mut(&mut self, row: TableRow) -> PtrMut<'_> { + debug_assert!(row.index() < self.data.len()); + self.data.get_unchecked_mut(row.index()) + } + + #[inline] + pub fn get_added_ticks(&self, row: TableRow) -> Option<&UnsafeCell> { + self.added_ticks.get(row.index()) + } + + #[inline] + pub fn get_changed_ticks(&self, row: TableRow) -> Option<&UnsafeCell> { + self.changed_ticks.get(row.index()) + } + + #[inline] + pub fn get_ticks(&self, row: TableRow) -> Option { + if row.index() < self.data.len() { + // SAFETY: The size of the column has already been checked. + Some(unsafe { self.get_ticks_unchecked(row) }) + } else { + None + } + } + + /// # Safety + /// index must be in-bounds + #[inline] + pub unsafe fn get_added_ticks_unchecked(&self, row: TableRow) -> &UnsafeCell { + debug_assert!(row.index() < self.added_ticks.len()); + self.added_ticks.get_unchecked(row.index()) } + /// # Safety + /// index must be in-bounds #[inline] - pub fn get_ticks(&self, row: usize) -> Option<&UnsafeCell> { - self.ticks.get(row) + pub unsafe fn get_changed_ticks_unchecked(&self, row: TableRow) -> &UnsafeCell { + debug_assert!(row.index() < self.changed_ticks.len()); + self.changed_ticks.get_unchecked(row.index()) } /// # Safety /// index must be in-bounds #[inline] - pub unsafe fn get_ticks_unchecked(&self, row: usize) -> &UnsafeCell { - debug_assert!(row < self.ticks.len()); - self.ticks.get_unchecked(row) + pub unsafe fn get_ticks_unchecked(&self, row: TableRow) -> ComponentTicks { + debug_assert!(row.index() < self.added_ticks.len()); + debug_assert!(row.index() < self.changed_ticks.len()); + ComponentTicks { + added: self.added_ticks.get_unchecked(row.index()).read(), + changed: self.changed_ticks.get_unchecked(row.index()).read(), + } } pub fn clear(&mut self) { self.data.clear(); - self.ticks.clear(); + self.added_ticks.clear(); + self.changed_ticks.clear(); } #[inline] pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { - for component_ticks in &mut self.ticks { - component_ticks.get_mut().check_ticks(change_tick); + for component_ticks in &mut self.added_ticks { + component_ticks.get_mut().check_tick(change_tick); + } + for component_ticks in &mut self.changed_ticks { + component_ticks.get_mut().check_tick(change_tick); } } } -pub struct Table { +/// A builder type for constructing [`Table`]s. +/// +/// - Use [`with_capacity`] to initialize the builder. +/// - Repeatedly call [`add_column`] to add columns for components. +/// - Finalize with [`build`] to get the constructed [`Table`]. +/// +/// [`with_capacity`]: Self::with_capacity +/// [`add_column`]: Self::add_column +/// [`build`]: Self::build +pub(crate) struct TableBuilder { columns: SparseSet, - entities: Vec, + capacity: usize, } -impl Table { - pub(crate) fn with_capacity(capacity: usize, column_capacity: usize) -> Table { +impl TableBuilder { + /// Creates a blank [`Table`], allocating space for `column_capacity` columns + /// with the capacity to hold `capacity` entities worth of components each. + pub fn with_capacity(capacity: usize, column_capacity: usize) -> Self { Self { columns: SparseSet::with_capacity(column_capacity), - entities: Vec::with_capacity(capacity), + capacity, } } - #[inline] - pub fn entities(&self) -> &[Entity] { - &self.entities - } - - pub(crate) fn add_column(&mut self, component_info: &ComponentInfo) { + pub fn add_column(&mut self, component_info: &ComponentInfo) { self.columns.insert( component_info.id(), - Column::with_capacity(component_info, self.entities.capacity()), + Column::with_capacity(component_info, self.capacity), ); } + pub fn build(self) -> Table { + Table { + columns: self.columns.into_immutable(), + entities: Vec::with_capacity(self.capacity), + } + } +} + +/// A column-oriented [structure-of-arrays] based storage for [`Component`]s of entities +/// in a [`World`]. +/// +/// Conceptually, a `Table` can be thought of as an `HashMap`, where +/// each `Column` is a type-erased `Vec`. Each row corresponds to a single entity +/// (i.e. index 3 in Column A and index 3 in Column B point to different components on the same +/// entity). Fetching components from a table involves fetching the associated column for a +/// component type (via it's [`ComponentId`]), then fetching the entity's row within that column. +/// +/// [structure-of-arrays]: https://en.wikipedia.org/wiki/AoS_and_SoA#Structure_of_arrays +/// [`Component`]: crate::component::Component +/// [`World`]: crate::world::World +pub struct Table { + columns: ImmutableSparseSet, + entities: Vec, +} + +impl Table { + #[inline] + pub fn entities(&self) -> &[Entity] { + &self.entities + } + /// Removes the entity at the given row and returns the entity swapped in to replace it (if an /// entity was swapped in) /// /// # Safety /// `row` must be in-bounds - pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: usize) -> Option { + pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: TableRow) -> Option { for column in self.columns.values_mut() { column.swap_remove_unchecked(row); } - let is_last = row == self.entities.len() - 1; - self.entities.swap_remove(row); + let is_last = row.index() == self.entities.len() - 1; + self.entities.swap_remove(row.index()); if is_last { None } else { - Some(self.entities[row]) + Some(self.entities[row.index()]) } } @@ -314,12 +459,12 @@ impl Table { /// Row must be in-bounds pub(crate) unsafe fn move_to_and_forget_missing_unchecked( &mut self, - row: usize, + row: TableRow, new_table: &mut Table, ) -> TableMoveResult { - debug_assert!(row < self.entity_count()); - let is_last = row == self.entities.len() - 1; - let new_row = new_table.allocate(self.entities.swap_remove(row)); + debug_assert!(row.index() < self.entity_count()); + let is_last = row.index() == self.entities.len() - 1; + let new_row = new_table.allocate(self.entities.swap_remove(row.index())); for (component_id, column) in self.columns.iter_mut() { if let Some(new_column) = new_table.get_column_mut(*component_id) { new_column.initialize_from_unchecked(column, row, new_row); @@ -333,7 +478,7 @@ impl Table { swapped_entity: if is_last { None } else { - Some(self.entities[row]) + Some(self.entities[row.index()]) }, } } @@ -346,12 +491,12 @@ impl Table { /// row must be in-bounds pub(crate) unsafe fn move_to_and_drop_missing_unchecked( &mut self, - row: usize, + row: TableRow, new_table: &mut Table, ) -> TableMoveResult { - debug_assert!(row < self.entity_count()); - let is_last = row == self.entities.len() - 1; - let new_row = new_table.allocate(self.entities.swap_remove(row)); + debug_assert!(row.index() < self.entity_count()); + let is_last = row.index() == self.entities.len() - 1; + let new_row = new_table.allocate(self.entities.swap_remove(row.index())); for (component_id, column) in self.columns.iter_mut() { if let Some(new_column) = new_table.get_column_mut(*component_id) { new_column.initialize_from_unchecked(column, row, new_row); @@ -364,7 +509,7 @@ impl Table { swapped_entity: if is_last { None } else { - Some(self.entities[row]) + Some(self.entities[row.index()]) }, } } @@ -377,12 +522,12 @@ impl Table { /// `row` must be in-bounds. `new_table` must contain every component this table has pub(crate) unsafe fn move_to_superset_unchecked( &mut self, - row: usize, + row: TableRow, new_table: &mut Table, ) -> TableMoveResult { - debug_assert!(row < self.entity_count()); - let is_last = row == self.entities.len() - 1; - let new_row = new_table.allocate(self.entities.swap_remove(row)); + debug_assert!(row.index() < self.entity_count()); + let is_last = row.index() == self.entities.len() - 1; + let new_row = new_table.allocate(self.entities.swap_remove(row.index())); for (component_id, column) in self.columns.iter_mut() { new_table .get_column_mut(*component_id) @@ -394,7 +539,7 @@ impl Table { swapped_entity: if is_last { None } else { - Some(self.entities[row]) + Some(self.entities[row.index()]) }, } } @@ -431,15 +576,16 @@ impl Table { /// /// # Safety /// the allocated row must be written to immediately with valid values in each column - pub(crate) unsafe fn allocate(&mut self, entity: Entity) -> usize { + pub(crate) unsafe fn allocate(&mut self, entity: Entity) -> TableRow { self.reserve(1); let index = self.entities.len(); self.entities.push(entity); for column in self.columns.values_mut() { column.data.set_len(self.entities.len()); - column.ticks.push(UnsafeCell::new(ComponentTicks::new(0))); + column.added_ticks.push(UnsafeCell::new(Tick::new(0))); + column.changed_ticks.push(UnsafeCell::new(Tick::new(0))); } - index + TableRow::new(index) } #[inline] @@ -457,11 +603,6 @@ impl Table { self.entities.capacity() } - #[inline] - pub fn component_capacity(&self) -> usize { - self.columns.capacity() - } - #[inline] pub fn is_empty(&self) -> bool { self.entities.is_empty() @@ -495,7 +636,7 @@ pub struct Tables { impl Default for Tables { fn default() -> Self { - let empty_table = Table::with_capacity(0, 0); + let empty_table = TableBuilder::with_capacity(0, 0).build(); Tables { tables: vec![empty_table], table_ids: HashMap::default(), @@ -505,7 +646,7 @@ impl Default for Tables { pub(crate) struct TableMoveResult { pub swapped_entity: Option, - pub new_row: usize, + pub new_row: TableRow, } impl Tables { @@ -548,12 +689,12 @@ impl Tables { .raw_entry_mut() .from_key(component_ids) .or_insert_with(|| { - let mut table = Table::with_capacity(0, component_ids.len()); + let mut table = TableBuilder::with_capacity(0, component_ids.len()); for component_id in component_ids { table.add_column(components.get_info_unchecked(*component_id)); } - tables.push(table); - (component_ids.to_vec(), TableId(tables.len() - 1)) + tables.push(table.build()); + (component_ids.to_vec(), TableId::new(tables.len() - 1)) }); *value @@ -599,9 +740,9 @@ mod tests { use crate::ptr::OwningPtr; use crate::storage::Storages; use crate::{ - component::{ComponentTicks, Components}, + component::{Components, Tick}, entity::Entity, - storage::Table, + storage::{TableBuilder, TableRow}, }; #[derive(Component)] struct W(T); @@ -610,21 +751,22 @@ mod tests { fn table() { let mut components = Components::default(); let mut storages = Storages::default(); - let component_id = components.init_component::>(&mut storages); + let component_id = components.init_component::>(&mut storages); let columns = &[component_id]; - let mut table = Table::with_capacity(0, columns.len()); - table.add_column(components.get_info(component_id).unwrap()); + let mut builder = TableBuilder::with_capacity(0, columns.len()); + builder.add_column(components.get_info(component_id).unwrap()); + let mut table = builder.build(); let entities = (0..200).map(Entity::from_raw).collect::>(); for entity in &entities { // SAFETY: we allocate and immediately set data afterwards unsafe { let row = table.allocate(*entity); - let value: W = W(row); + let value: W = W(row); OwningPtr::make(value, |value_ptr| { table.get_column_mut(component_id).unwrap().initialize( row, value_ptr, - ComponentTicks::new(0), + Tick::new(0), ); }); }; diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 48e8b6097d419..9f1c46a2e7b2b 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -243,16 +243,6 @@ impl<'w, 's> Commands<'w, 's> { e } - #[deprecated( - since = "0.9.0", - note = "Use `spawn` instead, which now accepts bundles, components, and tuples of bundles and components." - )] - pub fn spawn_bundle<'a, T: Bundle>(&'a mut self, bundle: T) -> EntityCommands<'w, 's, 'a> { - let mut e = self.spawn_empty(); - e.insert(bundle); - e - } - /// Returns the [`EntityCommands`] for the requested [`Entity`]. /// /// # Panics @@ -397,7 +387,7 @@ impl<'w, 's> Commands<'w, 's> { /// /// This method is equivalent to iterating `bundles_iter`, /// calling [`get_or_spawn`](Self::get_or_spawn) for each bundle, - /// and passing it to [`insert_bundle`](EntityCommands::insert_bundle), + /// and passing it to [`insert`](EntityCommands::insert), /// but it is faster due to memory pre-allocation. /// /// # Note @@ -538,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, @@ -620,14 +689,6 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> { self } - #[deprecated( - since = "0.9.0", - note = "Use `insert` instead, which now accepts bundles, components, and tuples of bundles and components." - )] - pub fn insert_bundle(&mut self, bundle: impl Bundle) -> &mut Self { - self.insert(bundle) - } - /// Removes a [`Bundle`] of components from the entity. /// /// See [`EntityMut::remove`](crate::world::EntityMut::remove) for more @@ -677,17 +738,6 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> { self } - #[deprecated( - since = "0.9.0", - note = "Use `remove` instead, which now accepts bundles, components, and tuples of bundles and components." - )] - pub fn remove_bundle(&mut self) -> &mut Self - where - T: Bundle, - { - self.remove::() - } - /// Despawns the entity. /// /// See [`World::despawn`] for more details. @@ -719,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 @@ -738,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/commands/parallel_scope.rs b/crates/bevy_ecs/src/system/commands/parallel_scope.rs index 573afa70aae51..72c8118aa5152 100644 --- a/crates/bevy_ecs/src/system/commands/parallel_scope.rs +++ b/crates/bevy_ecs/src/system/commands/parallel_scope.rs @@ -5,7 +5,7 @@ use thread_local::ThreadLocal; use crate::{ entity::Entities, prelude::World, - system::{SystemParam, SystemParamFetch, SystemParamState}, + system::{SystemMeta, SystemParam, SystemParamState}, }; use super::{CommandQueue, Commands}; @@ -49,36 +49,38 @@ pub struct ParallelCommands<'w, 's> { } impl SystemParam for ParallelCommands<'_, '_> { - type Fetch = ParallelCommandsState; -} - -impl<'w, 's> SystemParamFetch<'w, 's> for ParallelCommandsState { - type Item = ParallelCommands<'w, 's>; - - unsafe fn get_param( - state: &'s mut Self, - _: &crate::system::SystemMeta, - world: &'w World, - _: u32, - ) -> Self::Item { - ParallelCommands { - state, - entities: world.entities(), - } - } + type State = ParallelCommandsState; } // SAFETY: no component or resource access to report unsafe impl SystemParamState for ParallelCommandsState { + type Item<'w, 's> = ParallelCommands<'w, 's>; + fn init(_: &mut World, _: &mut crate::system::SystemMeta) -> Self { Self::default() } - fn apply(&mut self, world: &mut World) { + fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) { + #[cfg(feature = "trace")] + let _system_span = + bevy_utils::tracing::info_span!("system_commands", name = _system_meta.name()) + .entered(); for cq in &mut self.thread_local_storage { cq.get_mut().apply(world); } } + + unsafe fn get_param<'w, 's>( + state: &'s mut Self, + _: &crate::system::SystemMeta, + world: &'w World, + _: u32, + ) -> Self::Item<'w, 's> { + ParallelCommands { + state, + entities: world.entities(), + } + } } impl<'w, 's> ParallelCommands<'w, 's> { diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index 662d031daa2e3..8b1607c75898e 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -5,9 +5,8 @@ use crate::{ query::Access, schedule::{SystemLabel, SystemLabelId}, system::{ - check_system_change_tick, AsSystemLabel, ExclusiveSystemParam, ExclusiveSystemParamFetch, - ExclusiveSystemParamItem, ExclusiveSystemParamState, IntoSystem, System, SystemMeta, - SystemTypeIdLabel, + check_system_change_tick, AsSystemLabel, ExclusiveSystemParam, ExclusiveSystemParamItem, + ExclusiveSystemParamState, IntoSystem, System, SystemMeta, SystemTypeIdLabel, }, world::{World, WorldId}, }; @@ -25,7 +24,7 @@ where Param: ExclusiveSystemParam, { func: F, - param_state: Option, + param_state: Option, system_meta: SystemMeta, world_id: Option, // NOTE: PhantomData T> gives this safe Send/Sync impls @@ -95,7 +94,7 @@ where let saved_last_tick = world.last_change_tick; world.last_change_tick = self.system_meta.last_change_tick; - let params = ::Fetch::get_param( + let params = ::State::get_param( self.param_state.as_mut().expect(PARAM_MESSAGE), &self.system_meta, ); @@ -130,7 +129,7 @@ where fn initialize(&mut self, world: &mut World) { self.world_id = Some(world.id()); self.system_meta.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE); - self.param_state = Some(::init( + self.param_state = Some(::init( world, &mut self.system_meta, )); diff --git a/crates/bevy_ecs/src/system/exclusive_system_param.rs b/crates/bevy_ecs/src/system/exclusive_system_param.rs index 412474bda03dd..6c52738901254 100644 --- a/crates/bevy_ecs/src/system/exclusive_system_param.rs +++ b/crates/bevy_ecs/src/system/exclusive_system_param.rs @@ -8,111 +8,86 @@ use bevy_ecs_macros::all_tuples; use bevy_utils::synccell::SyncCell; pub trait ExclusiveSystemParam: Sized { - type Fetch: for<'s> ExclusiveSystemParamFetch<'s>; + type State: ExclusiveSystemParamState; } pub type ExclusiveSystemParamItem<'s, P> = - <

::Fetch as ExclusiveSystemParamFetch<'s>>::Item; + <

::State as ExclusiveSystemParamState>::Item<'s>; /// The state of a [`SystemParam`]. -pub trait ExclusiveSystemParamState: Send + Sync { +pub trait ExclusiveSystemParamState: Send + Sync + 'static { + type Item<'s>: ExclusiveSystemParam; + fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self; #[inline] fn apply(&mut self, _world: &mut World) {} -} -pub trait ExclusiveSystemParamFetch<'state>: ExclusiveSystemParamState { - type Item: ExclusiveSystemParam; - fn get_param(state: &'state mut Self, system_meta: &SystemMeta) -> Self::Item; + fn get_param<'s>(state: &'s mut Self, system_meta: &SystemMeta) -> Self::Item<'s>; } impl<'a, Q: WorldQuery + 'static, F: ReadOnlyWorldQuery + 'static> ExclusiveSystemParam for &'a mut QueryState { - type Fetch = QueryState; -} - -impl<'s, Q: WorldQuery + 'static, F: ReadOnlyWorldQuery + 'static> ExclusiveSystemParamFetch<'s> - for QueryState -{ - type Item = &'s mut QueryState; - - fn get_param(state: &'s mut Self, _system_meta: &SystemMeta) -> Self::Item { - state - } + type State = QueryState; } impl ExclusiveSystemParamState for QueryState { + type Item<'s> = &'s mut QueryState; + fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self { QueryState::new(world) } -} - -impl<'a, P: SystemParam + 'static> ExclusiveSystemParam for &'a mut SystemState

{ - type Fetch = SystemState

; -} -impl<'s, P: SystemParam + 'static> ExclusiveSystemParamFetch<'s> for SystemState

{ - type Item = &'s mut SystemState

; - - fn get_param(state: &'s mut Self, _system_meta: &SystemMeta) -> Self::Item { + fn get_param<'s>(state: &'s mut Self, _system_meta: &SystemMeta) -> Self::Item<'s> { state } } +impl<'a, P: SystemParam + 'static> ExclusiveSystemParam for &'a mut SystemState

{ + type State = SystemState

; +} + impl ExclusiveSystemParamState for SystemState

{ + type Item<'s> = &'s mut SystemState

; + fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self { SystemState::new(world) } -} -impl<'s, T: FromWorld + Send + Sync + 'static> ExclusiveSystemParam for Local<'s, T> { - type Fetch = LocalState; + fn get_param<'s>(state: &'s mut Self, _system_meta: &SystemMeta) -> Self::Item<'s> { + state + } } -impl<'s, T: FromWorld + Send + Sync + 'static> ExclusiveSystemParamFetch<'s> for LocalState { - type Item = Local<'s, T>; - - fn get_param(state: &'s mut Self, _system_meta: &SystemMeta) -> Self::Item { - Local(state.0.get()) - } +impl<'s, T: FromWorld + Send + Sync + 'static> ExclusiveSystemParam for Local<'s, T> { + type State = LocalState; } impl ExclusiveSystemParamState for LocalState { + type Item<'s> = Local<'s, T>; + fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self { Self(SyncCell::new(T::from_world(world))) } + + fn get_param<'s>(state: &'s mut Self, _system_meta: &SystemMeta) -> Self::Item<'s> { + Local(state.0.get()) + } } macro_rules! impl_exclusive_system_param_tuple { ($($param: ident),*) => { impl<$($param: ExclusiveSystemParam),*> ExclusiveSystemParam for ($($param,)*) { - type Fetch = ($($param::Fetch,)*); + type State = ($($param::State,)*); } #[allow(unused_variables)] #[allow(non_snake_case)] - impl<'s, $($param: ExclusiveSystemParamFetch<'s>),*> ExclusiveSystemParamFetch<'s> for ($($param,)*) { - type Item = ($($param::Item,)*); - - #[inline] - #[allow(clippy::unused_unit)] - fn get_param( - state: &'s mut Self, - system_meta: &SystemMeta, - ) -> Self::Item { - - let ($($param,)*) = state; - ($($param::get_param($param, system_meta),)*) - } - } - - // SAFETY: implementors of each `ExclusiveSystemParamState` in the tuple have validated their impls - #[allow(clippy::undocumented_unsafe_blocks)] // false positive by clippy - #[allow(non_snake_case)] impl<$($param: ExclusiveSystemParamState),*> ExclusiveSystemParamState for ($($param,)*) { + type Item<'s> = ($($param::Item<'s>,)*); + #[inline] fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { (($($param::init(_world, _system_meta),)*)) @@ -123,7 +98,19 @@ macro_rules! impl_exclusive_system_param_tuple { let ($($param,)*) = self; $($param.apply(_world);)* } + + #[inline] + #[allow(clippy::unused_unit)] + fn get_param<'s>( + state: &'s mut Self, + system_meta: &SystemMeta, + ) -> Self::Item<'s> { + + let ($($param,)*) = state; + ($($param::get_param($param, system_meta),)*) + } } + }; } diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index c3f672e718734..2cc88f9e30b77 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -6,8 +6,8 @@ use crate::{ query::{Access, FilteredAccessSet}, schedule::{SystemLabel, SystemLabelId}, system::{ - check_system_change_tick, ReadOnlySystemParamFetch, System, SystemParam, SystemParamFetch, - SystemParamItem, SystemParamState, + check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem, + SystemParamState, }, world::{World, WorldId}, }; @@ -37,6 +37,12 @@ impl SystemMeta { } } + /// Returns the system's name + #[inline] + pub fn name(&self) -> &str { + &self.name + } + /// Returns true if the system is [`Send`]. #[inline] pub fn is_send(&self) -> bool { @@ -135,7 +141,7 @@ impl SystemMeta { /// ``` pub struct SystemState { meta: SystemMeta, - param_state: ::Fetch, + param_state: ::State, world_id: WorldId, archetype_generation: ArchetypeGeneration, } @@ -144,7 +150,7 @@ impl SystemState { pub fn new(world: &mut World) -> Self { let mut meta = SystemMeta::new::(); meta.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE); - let param_state = ::init(world, &mut meta); + let param_state = ::init(world, &mut meta); Self { meta, param_state, @@ -160,12 +166,9 @@ impl SystemState { /// Retrieve the [`SystemParam`] values. This can only be called when all parameters are read-only. #[inline] - pub fn get<'w, 's>( - &'s mut self, - world: &'w World, - ) -> >::Item + pub fn get<'w, 's>(&'s mut self, world: &'w World) -> SystemParamItem<'w, 's, Param> where - Param::Fetch: ReadOnlySystemParamFetch, + Param: ReadOnlySystemParam, { self.validate_world_and_update_archetypes(world); // SAFETY: Param is read-only and doesn't allow mutable access to World. It also matches the World this SystemState was created with. @@ -174,10 +177,7 @@ impl SystemState { /// Retrieve the mutable [`SystemParam`] values. #[inline] - pub fn get_mut<'w, 's>( - &'s mut self, - world: &'w mut World, - ) -> >::Item { + pub fn get_mut<'w, 's>(&'s mut self, world: &'w mut World) -> SystemParamItem<'w, 's, Param> { self.validate_world_and_update_archetypes(world); // SAFETY: World is uniquely borrowed and matches the World this SystemState was created with. unsafe { self.get_unchecked_manual(world) } @@ -188,7 +188,7 @@ impl SystemState { /// This function should be called manually after the values returned by [`SystemState::get`] and [`SystemState::get_mut`] /// are finished being used. pub fn apply(&mut self, world: &mut World) { - self.param_state.apply(world); + self.param_state.apply(&self.meta, world); } #[inline] @@ -221,9 +221,9 @@ impl SystemState { pub unsafe fn get_unchecked_manual<'w, 's>( &'s mut self, world: &'w World, - ) -> >::Item { + ) -> SystemParamItem<'w, 's, Param> { let change_tick = world.increment_change_tick(); - let param = ::get_param( + let param = ::get_param( &mut self.param_state, &self.meta, world, @@ -315,7 +315,7 @@ where Param: SystemParam, { func: F, - param_state: Option, + param_state: Option, system_meta: SystemMeta, world_id: Option, archetype_generation: ArchetypeGeneration, @@ -400,7 +400,7 @@ where // We update the archetype component access correctly based on `Param`'s requirements // in `update_archetype_component_access`. // Our caller upholds the requirements. - let params = ::Fetch::get_param( + let params = ::State::get_param( self.param_state.as_mut().expect(Self::PARAM_MESSAGE), &self.system_meta, world, @@ -422,14 +422,14 @@ where #[inline] fn apply_buffers(&mut self, world: &mut World) { let param_state = self.param_state.as_mut().expect(Self::PARAM_MESSAGE); - param_state.apply(world); + param_state.apply(&self.system_meta, world); } #[inline] fn initialize(&mut self, world: &mut World) { self.world_id = Some(world.id()); self.system_meta.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE); - self.param_state = Some(::init( + self.param_state = Some(::init( world, &mut self.system_meta, )); @@ -588,7 +588,7 @@ macro_rules! impl_system_function { where for <'a> &'a mut Func: FnMut(In, $($param),*) -> Out + - FnMut(In, $(<<$param as SystemParam>::Fetch as SystemParamFetch>::Item),*) -> Out, Out: 'static + FnMut(In, $(SystemParamItem<$param>),*) -> Out, Out: 'static { #[inline] fn run(&mut self, input: Input, param_value: SystemParamItem< ($($param,)*)>) -> Out { diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 0dcc806c6d44a..be66144b4a6a8 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 fa5f5bf517785..8c9ff2ddfa01b 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -3,7 +3,7 @@ use crate::{ archetype::{Archetype, Archetypes}, bundle::Bundles, change_detection::Ticks, - component::{Component, ComponentId, ComponentTicks, Components}, + component::{Component, ComponentId, ComponentTicks, Components, Tick}, entity::{Entities, Entity}, query::{ Access, FilteredAccess, FilteredAccessSet, QueryState, ReadOnlyWorldQuery, WorldQuery, @@ -33,19 +33,15 @@ use std::{ /// See the *Generic `SystemParam`s* section for details and workarounds of the probable /// cause if this derive causes an error to be emitted. /// -/// -/// The struct for which `SystemParam` is derived must (currently) have exactly -/// two lifetime parameters. -/// The first is the lifetime of the world, and the second the lifetime -/// of the parameter's state. +/// Derived `SystemParam` structs may have two lifetimes: `'w` for data stored in the [`World`], +/// and `'s` for data stored in the parameter's state. /// /// ## Attributes /// /// `#[system_param(ignore)]`: /// Can be added to any field in the struct. Fields decorated with this attribute /// will be created with the default value upon realisation. -/// This is most useful for `PhantomData` fields, to ensure that the required lifetimes are -/// used, as shown in the example. +/// This is most useful for `PhantomData` fields, such as markers for generic types. /// /// # Example /// @@ -57,17 +53,17 @@ use std::{ /// use bevy_ecs::system::SystemParam; /// /// #[derive(SystemParam)] -/// struct MyParam<'w, 's> { +/// struct MyParam<'w, Marker: 'static> { /// foo: Res<'w, SomeResource>, /// #[system_param(ignore)] -/// marker: PhantomData<&'s ()>, +/// marker: PhantomData, /// } /// -/// fn my_system(param: MyParam) { +/// fn my_system(param: MyParam) { /// // Access the resource through `param.foo` /// } /// -/// # bevy_ecs::system::assert_is_system(my_system); +/// # bevy_ecs::system::assert_is_system(my_system::<()>); /// ``` /// /// # Generic `SystemParam`s @@ -76,7 +72,7 @@ use std::{ /// /// ```text /// expected ... [ParamType] -/// found associated type `<<[ParamType] as SystemParam>::Fetch as SystemParamFetch<'_, '_>>::Item` +/// found associated type `<<[ParamType] as SystemParam>::State as SystemParamState>::Item<'_, '_>` /// ``` /// where `[ParamType]` is the type of one of your fields. /// To solve this error, you can wrap the field of type `[ParamType]` with [`StaticSystemParam`] @@ -85,7 +81,7 @@ use std::{ /// ## Details /// /// The derive macro requires that the [`SystemParam`] implementation of -/// each field `F`'s [`Fetch`](`SystemParam::Fetch`)'s [`Item`](`SystemParamFetch::Item`) is itself `F` +/// each field `F`'s [`State`](`SystemParam::State`)'s [`Item`](`SystemParamState::Item`) is itself `F` /// (ignoring lifetimes for simplicity). /// This assumption is due to type inference reasons, so that the derived [`SystemParam`] can be /// used as an argument to a function system. @@ -93,18 +89,50 @@ use std::{ /// /// This will most commonly occur when working with `SystemParam`s generically, as the requirement /// has not been proven to the compiler. +/// +/// # `!Sync` Resources +/// A `!Sync` type cannot implement `Resource`. However, it is possible to wrap a `Send` but not `Sync` +/// type in [`SyncCell`] or the currently unstable [`Exclusive`] to make it `Sync`. This forces only +/// having mutable access (`&mut T` only, never `&T`), but makes it safe to reference across multiple +/// threads. +/// +/// This will fail to compile since `RefCell` is `!Sync`. +/// ```compile_fail +/// # use std::cell::RefCell; +/// # use bevy_ecs::system::Resource; +/// +/// #[derive(Resource)] +/// struct NotSync { +/// counter: RefCell, +/// } +/// ``` +/// +/// This will compile since the `RefCell` is wrapped with `SyncCell`. +/// ``` +/// # use std::cell::RefCell; +/// # use bevy_ecs::system::Resource; +/// use bevy_utils::synccell::SyncCell; +/// +/// #[derive(Resource)] +/// struct ActuallySync { +/// counter: SyncCell>, +/// } +/// ``` +/// +/// [`SyncCell`]: bevy_utils::synccell::SyncCell +/// [`Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html pub trait SystemParam: Sized { - type Fetch: for<'w, 's> SystemParamFetch<'w, 's>; + type State: SystemParamState; } -pub type SystemParamItem<'w, 's, P> = <

::Fetch as SystemParamFetch<'w, 's>>::Item; +pub type SystemParamItem<'w, 's, P> = <

::State as SystemParamState>::Item<'w, 's>; /// The state of a [`SystemParam`]. /// /// # Safety /// /// It is the implementor's responsibility to ensure `system_meta` is populated with the _exact_ -/// [`World`] access used by the [`SystemParamState`] (and associated [`SystemParamFetch`]). +/// [`World`] access used by the [`SystemParamState`]. /// Additionally, it is the implementor's responsibility to ensure there is no /// conflicting access across all [`SystemParam`]'s. pub unsafe trait SystemParamState: Send + Sync + 'static { @@ -112,38 +140,37 @@ pub unsafe trait SystemParamState: Send + Sync + 'static { #[inline] fn new_archetype(&mut self, _archetype: &Archetype, _system_meta: &mut SystemMeta) {} #[inline] - fn apply(&mut self, _world: &mut World) {} -} - -/// A [`SystemParamFetch`] that only reads a given [`World`]. -/// -/// # Safety -/// This must only be implemented for [`SystemParamFetch`] impls that exclusively read the World passed in to [`SystemParamFetch::get_param`] -pub unsafe trait ReadOnlySystemParamFetch {} + #[allow(unused_variables)] + fn apply(&mut self, system_meta: &SystemMeta, _world: &mut World) {} -pub trait SystemParamFetch<'world, 'state>: SystemParamState { - type Item: SystemParam; + type Item<'world, 'state>: SystemParam; /// # Safety /// /// This call might access any of the input parameters in an unsafe way. Make sure the data /// access is safe in the context of the system scheduler. - unsafe fn get_param( + unsafe fn get_param<'world, 'state>( state: &'state mut Self, system_meta: &SystemMeta, world: &'world World, change_tick: u32, - ) -> Self::Item; + ) -> Self::Item<'world, 'state>; } +/// A [`SystemParam`] that only reads a given [`World`]. +/// +/// # Safety +/// This must only be implemented for [`SystemParam`] impls that exclusively read the World passed in to [`SystemParamState::get_param`] +pub unsafe trait ReadOnlySystemParam: SystemParam {} + impl<'w, 's, Q: WorldQuery + 'static, F: ReadOnlyWorldQuery + 'static> SystemParam for Query<'w, 's, Q, F> { - type Fetch = QueryState; + type State = QueryState; } // SAFETY: QueryState is constrained to read-only fetches, so it only reads World. -unsafe impl ReadOnlySystemParamFetch - for QueryState +unsafe impl<'w, 's, Q: ReadOnlyWorldQuery + 'static, F: ReadOnlyWorldQuery + 'static> + ReadOnlySystemParam for Query<'w, 's, Q, F> { } @@ -152,6 +179,8 @@ unsafe impl ReadOnlySystemParamFet unsafe impl SystemParamState for QueryState { + type Item<'w, 's> = Query<'w, 's, Q, F>; + fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { let state = QueryState::new(world); assert_component_access_compatibility( @@ -177,20 +206,14 @@ unsafe impl SystemPara .archetype_component_access .extend(&self.archetype_component_access); } -} - -impl<'w, 's, Q: WorldQuery + 'static, F: ReadOnlyWorldQuery + 'static> SystemParamFetch<'w, 's> - for QueryState -{ - type Item = Query<'w, 's, Q, F>; #[inline] - unsafe fn get_param( + unsafe fn get_param<'w, 's>( state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { Query::new(world, state, system_meta.last_change_tick, change_tick) } } @@ -216,14 +239,122 @@ 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::Fetch, + param_states: &'s mut T::State, world: &'w World, system_meta: SystemMeta, change_tick: u32, } /// The [`SystemParamState`] of [`ParamSet`]. -pub struct ParamSetState SystemParamFetch<'w, 's>>(T); +pub struct ParamSetState(T); impl_param_set!(); @@ -273,13 +404,14 @@ pub trait Resource: Send + Sync + 'static {} /// Use `Option>` instead if the resource might not always exist. pub struct Res<'w, T: Resource> { value: &'w T, - ticks: &'w ComponentTicks, + added: &'w Tick, + changed: &'w Tick, last_change_tick: u32, change_tick: u32, } // SAFETY: Res only reads a single World resource -unsafe impl ReadOnlySystemParamFetch for ResState {} +unsafe impl<'w, T: Resource> ReadOnlySystemParam for Res<'w, T> {} impl<'w, T: Resource> Debug for Res<'w, T> where @@ -296,7 +428,8 @@ impl<'w, T: Resource> Res<'w, T> { pub fn clone(this: &Self) -> Self { Self { value: this.value, - ticks: this.ticks, + added: this.added, + changed: this.changed, last_change_tick: this.last_change_tick, change_tick: this.change_tick, } @@ -304,13 +437,14 @@ impl<'w, T: Resource> Res<'w, T> { /// Returns `true` if the resource was added after the system last ran. pub fn is_added(&self) -> bool { - self.ticks.is_added(self.last_change_tick, self.change_tick) + self.added + .is_older_than(self.last_change_tick, self.change_tick) } /// Returns `true` if the resource was added or mutably dereferenced after the system last ran. pub fn is_changed(&self) -> bool { - self.ticks - .is_changed(self.last_change_tick, self.change_tick) + self.changed + .is_older_than(self.last_change_tick, self.change_tick) } pub fn into_inner(self) -> &'w T { @@ -337,7 +471,8 @@ impl<'w, T: Resource> From> for Res<'w, T> { fn from(res: ResMut<'w, T>) -> Self { Self { value: res.value, - ticks: res.ticks.component_ticks, + added: res.ticks.added, + changed: res.ticks.changed, change_tick: res.ticks.change_tick, last_change_tick: res.ticks.last_change_tick, } @@ -364,22 +499,26 @@ pub struct ResState { } impl<'a, T: Resource> SystemParam for Res<'a, T> { - type Fetch = ResState; + type State = ResState; } // SAFETY: Res ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this Res // conflicts with any prior access, a panic will occur. unsafe impl SystemParamState for ResState { + type Item<'w, 's> = Res<'w, T>; + fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { let component_id = world.initialize_resource::(); - let combined_access = system_meta.component_access_set.combined_access_mut(); + let combined_access = system_meta.component_access_set.combined_access(); assert!( !combined_access.has_write(component_id), "error[B0002]: Res<{}> in system {} conflicts with a previous ResMut<{0}> access. Consider removing the duplicate access.", std::any::type_name::(), system_meta.name, ); - combined_access.add_read(component_id); + system_meta + .component_access_set + .add_unfiltered_read(component_id); let archetype_component_id = world .get_resource_archetype_component_id(component_id) @@ -392,18 +531,14 @@ unsafe impl SystemParamState for ResState { marker: PhantomData, } } -} - -impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for ResState { - type Item = Res<'w, T>; #[inline] - unsafe fn get_param( + unsafe fn get_param<'w, 's>( state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { let (ptr, ticks) = world .get_resource_with_ticks(state.component_id) .unwrap_or_else(|| { @@ -415,7 +550,8 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for ResState { }); Res { value: ptr.deref(), - ticks: ticks.deref(), + added: ticks.added.deref(), + changed: ticks.changed.deref(), last_change_tick: system_meta.last_change_tick, change_tick, } @@ -428,35 +564,34 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for ResState { pub struct OptionResState(ResState); impl<'a, T: Resource> SystemParam for Option> { - type Fetch = OptionResState; + type State = OptionResState; } // SAFETY: Only reads a single World resource -unsafe impl ReadOnlySystemParamFetch for OptionResState {} +unsafe impl<'a, T: Resource> ReadOnlySystemParam for Option> {} // SAFETY: this impl defers to `ResState`, which initializes // and validates the correct world access unsafe impl SystemParamState for OptionResState { + type Item<'w, 's> = Option>; + fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { Self(ResState::init(world, system_meta)) } -} - -impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for OptionResState { - type Item = Option>; #[inline] - unsafe fn get_param( + unsafe fn get_param<'w, 's>( state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { world .get_resource_with_ticks(state.0.component_id) .map(|(ptr, ticks)| Res { value: ptr.deref(), - ticks: ticks.deref(), + added: ticks.added.deref(), + changed: ticks.changed.deref(), last_change_tick: system_meta.last_change_tick, change_tick, }) @@ -471,15 +606,17 @@ pub struct ResMutState { } impl<'a, T: Resource> SystemParam for ResMut<'a, T> { - type Fetch = ResMutState; + type State = ResMutState; } // SAFETY: Res ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this Res // conflicts with any prior access, a panic will occur. unsafe impl SystemParamState for ResMutState { + type Item<'w, 's> = ResMut<'w, T>; + fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { let component_id = world.initialize_resource::(); - let combined_access = system_meta.component_access_set.combined_access_mut(); + let combined_access = system_meta.component_access_set.combined_access(); if combined_access.has_write(component_id) { panic!( "error[B0002]: ResMut<{}> in system {} conflicts with a previous ResMut<{0}> access. Consider removing the duplicate access.", @@ -489,7 +626,9 @@ unsafe impl SystemParamState for ResMutState { "error[B0002]: ResMut<{}> in system {} conflicts with a previous Res<{0}> access. Consider removing the duplicate access.", std::any::type_name::(), system_meta.name); } - combined_access.add_write(component_id); + system_meta + .component_access_set + .add_unfiltered_write(component_id); let archetype_component_id = world .get_resource_archetype_component_id(component_id) @@ -502,18 +641,14 @@ unsafe impl SystemParamState for ResMutState { marker: PhantomData, } } -} - -impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for ResMutState { - type Item = ResMut<'w, T>; #[inline] - unsafe fn get_param( + unsafe fn get_param<'w, 's>( state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { let value = world .get_resource_unchecked_mut_with_id(state.component_id) .unwrap_or_else(|| { @@ -526,7 +661,8 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for ResMutState { ResMut { value: value.value, ticks: Ticks { - component_ticks: value.ticks.component_ticks, + added: value.ticks.added, + changed: value.ticks.changed, last_change_tick: system_meta.last_change_tick, change_tick, }, @@ -540,33 +676,32 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for ResMutState { pub struct OptionResMutState(ResMutState); impl<'a, T: Resource> SystemParam for Option> { - type Fetch = OptionResMutState; + type State = OptionResMutState; } // SAFETY: this impl defers to `ResMutState`, which initializes // and validates the correct world access unsafe impl SystemParamState for OptionResMutState { + type Item<'w, 's> = Option>; + fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { Self(ResMutState::init(world, system_meta)) } -} - -impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for OptionResMutState { - type Item = Option>; #[inline] - unsafe fn get_param( + unsafe fn get_param<'w, 's>( state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { world .get_resource_unchecked_mut_with_id(state.0.component_id) .map(|value| ResMut { value: value.value, ticks: Ticks { - component_ticks: value.ticks.component_ticks, + added: value.ticks.added, + changed: value.ticks.changed, last_change_tick: system_meta.last_change_tick, change_tick, }, @@ -575,50 +710,54 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for OptionResMutState { } impl<'w, 's> SystemParam for Commands<'w, 's> { - type Fetch = CommandQueue; + type State = CommandQueue; } // SAFETY: Commands only accesses internal state -unsafe impl ReadOnlySystemParamFetch for CommandQueue {} +unsafe impl<'w, 's> ReadOnlySystemParam for Commands<'w, 's> {} // SAFETY: only local state is accessed unsafe impl SystemParamState for CommandQueue { + type Item<'w, 's> = Commands<'w, 's>; + fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { Default::default() } - fn apply(&mut self, world: &mut World) { + fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) { + #[cfg(feature = "trace")] + let _system_span = + bevy_utils::tracing::info_span!("system_commands", name = _system_meta.name()) + .entered(); self.apply(world); } -} - -impl<'w, 's> SystemParamFetch<'w, 's> for CommandQueue { - type Item = Commands<'w, 's>; #[inline] - unsafe fn get_param( + unsafe fn get_param<'w, 's>( state: &'s mut Self, _system_meta: &SystemMeta, world: &'w World, _change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { Commands::new(state, world) } } /// SAFETY: only reads world -unsafe impl ReadOnlySystemParamFetch for WorldState {} +unsafe impl<'w> ReadOnlySystemParam for &'w World {} /// The [`SystemParamState`] of [`&World`](crate::world::World). #[doc(hidden)] pub struct WorldState; impl<'w> SystemParam for &'w World { - type Fetch = WorldState; + type State = WorldState; } // SAFETY: `read_all` access is set and conflicts result in a panic unsafe impl SystemParamState for WorldState { + type Item<'w, 's> = &'w World; + fn init(_world: &mut World, system_meta: &mut SystemMeta) -> Self { let mut access = Access::default(); access.read_all(); @@ -644,16 +783,13 @@ unsafe impl SystemParamState for WorldState { WorldState } -} -impl<'w, 's> SystemParamFetch<'w, 's> for WorldState { - type Item = &'w World; - unsafe fn get_param( + unsafe fn get_param<'w, 's>( _state: &'s mut Self, _system_meta: &SystemMeta, world: &'w World, _change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { world } } @@ -704,7 +840,7 @@ impl<'w, 's> SystemParamFetch<'w, 's> for WorldState { pub struct Local<'a, T: FromWorld + Send + 'static>(pub(crate) &'a mut T); // SAFETY: Local only accesses internal state -unsafe impl ReadOnlySystemParamFetch for LocalState {} +unsafe impl<'a, T: FromWorld + Send + 'static> ReadOnlySystemParam for Local<'a, T> {} impl<'a, T: FromWorld + Send + Sync + 'static> Debug for Local<'a, T> where @@ -760,26 +896,24 @@ where pub struct LocalState(pub(crate) SyncCell); impl<'a, T: FromWorld + Send + 'static> SystemParam for Local<'a, T> { - type Fetch = LocalState; + type State = LocalState; } // SAFETY: only local state is accessed unsafe impl SystemParamState for LocalState { + type Item<'w, 's> = Local<'s, T>; + fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self { Self(SyncCell::new(T::from_world(world))) } -} - -impl<'w, 's, T: FromWorld + Send + 'static> SystemParamFetch<'w, 's> for LocalState { - type Item = Local<'s, T>; #[inline] - unsafe fn get_param( + unsafe fn get_param<'w, 's>( state: &'s mut Self, _system_meta: &SystemMeta, _world: &'w World, _change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { Local(state.0.get()) } } @@ -795,10 +929,9 @@ impl<'w, 's, T: FromWorld + Send + 'static> SystemParamFetch<'w, 's> for LocalSt /// note that the `RemovedComponents` list will not be automatically cleared for you, /// and will need to be manually flushed using [`World::clear_trackers`] /// -/// For users of `bevy` itself, this is automatically done in a system added by `MinimalPlugins` -/// or `DefaultPlugins` at the end of each pass of the game loop during the `CoreStage::Last` -/// stage. As such `RemovedComponents` systems should be scheduled after the stage where -/// removal occurs but before `CoreStage::Last`. +/// For users of `bevy` and `bevy_app`, this is automatically done in `bevy_app::App::update`. +/// For the main world, [`World::clear_trackers`] is run after the main schedule is run and after +/// `SubApp`'s have run. /// /// # Examples /// @@ -841,7 +974,7 @@ impl<'a, T: Component> IntoIterator for &'a RemovedComponents<'a, T> { } // SAFETY: Only reads World components -unsafe impl ReadOnlySystemParamFetch for RemovedComponentsState {} +unsafe impl<'a, T: Component> ReadOnlySystemParam for RemovedComponents<'a, T> {} /// The [`SystemParamState`] of [`RemovedComponents`]. #[doc(hidden)] @@ -851,30 +984,28 @@ pub struct RemovedComponentsState { } impl<'a, T: Component> SystemParam for RemovedComponents<'a, T> { - type Fetch = RemovedComponentsState; + type State = RemovedComponentsState; } // SAFETY: no component access. removed component entity collections can be read in parallel and are // never mutably borrowed during system execution unsafe impl SystemParamState for RemovedComponentsState { + type Item<'w, 's> = RemovedComponents<'w, T>; + fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self { Self { component_id: world.init_component::(), marker: PhantomData, } } -} - -impl<'w, 's, T: Component> SystemParamFetch<'w, 's> for RemovedComponentsState { - type Item = RemovedComponents<'w, T>; #[inline] - unsafe fn get_param( + unsafe fn get_param<'w, 's>( state: &'s mut Self, _system_meta: &SystemMeta, world: &'w World, _change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { RemovedComponents { world, component_id: state.component_id, @@ -903,7 +1034,7 @@ pub struct NonSend<'w, T: 'static> { } // SAFETY: Only reads a single World non-send resource -unsafe impl ReadOnlySystemParamFetch for NonSendState {} +unsafe impl<'w, T> ReadOnlySystemParam for NonSend<'w, T> {} impl<'w, T> Debug for NonSend<'w, T> where @@ -938,7 +1069,10 @@ impl<'a, T> From> for NonSend<'a, T> { fn from(nsm: NonSendMut<'a, T>) -> Self { Self { value: nsm.value, - ticks: nsm.ticks.component_ticks.to_owned(), + ticks: ComponentTicks { + added: nsm.ticks.added.to_owned(), + changed: nsm.ticks.changed.to_owned(), + }, change_tick: nsm.ticks.change_tick, last_change_tick: nsm.ticks.last_change_tick, } @@ -953,24 +1087,28 @@ pub struct NonSendState { } impl<'a, T: 'static> SystemParam for NonSend<'a, T> { - type Fetch = NonSendState; + type State = NonSendState; } // SAFETY: NonSendComponentId and ArchetypeComponentId access is applied to SystemMeta. If this // NonSend conflicts with any prior access, a panic will occur. unsafe impl SystemParamState for NonSendState { + type Item<'w, 's> = NonSend<'w, T>; + fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { system_meta.set_non_send(); let component_id = world.initialize_non_send_resource::(); - let combined_access = system_meta.component_access_set.combined_access_mut(); + let combined_access = system_meta.component_access_set.combined_access(); assert!( !combined_access.has_write(component_id), "error[B0002]: NonSend<{}> in system {} conflicts with a previous mutable resource access ({0}). Consider removing the duplicate access.", std::any::type_name::(), system_meta.name, ); - combined_access.add_read(component_id); + system_meta + .component_access_set + .add_unfiltered_read(component_id); let archetype_component_id = world .get_resource_archetype_component_id(component_id) @@ -983,18 +1121,14 @@ unsafe impl SystemParamState for NonSendState { marker: PhantomData, } } -} - -impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendState { - type Item = NonSend<'w, T>; #[inline] - unsafe fn get_param( + unsafe fn get_param<'w, 's>( state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { world.validate_non_send_access::(); let (ptr, ticks) = world .get_resource_with_ticks(state.component_id) @@ -1021,30 +1155,28 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendState { pub struct OptionNonSendState(NonSendState); impl<'w, T: 'static> SystemParam for Option> { - type Fetch = OptionNonSendState; + type State = OptionNonSendState; } // SAFETY: Only reads a single non-send resource -unsafe impl ReadOnlySystemParamFetch for OptionNonSendState {} +unsafe impl<'w, T: 'static> ReadOnlySystemParam for Option> {} // SAFETY: this impl defers to `NonSendState`, which initializes // and validates the correct world access unsafe impl SystemParamState for OptionNonSendState { + type Item<'w, 's> = Option>; + fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { Self(NonSendState::init(world, system_meta)) } -} - -impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for OptionNonSendState { - type Item = Option>; #[inline] - unsafe fn get_param( + unsafe fn get_param<'w, 's>( state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { world.validate_non_send_access::(); world .get_resource_with_ticks(state.0.component_id) @@ -1065,17 +1197,19 @@ pub struct NonSendMutState { } impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { - type Fetch = NonSendMutState; + type State = NonSendMutState; } // SAFETY: NonSendMut ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this // NonSendMut conflicts with any prior access, a panic will occur. unsafe impl SystemParamState for NonSendMutState { + type Item<'w, 's> = NonSendMut<'w, T>; + fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { system_meta.set_non_send(); let component_id = world.initialize_non_send_resource::(); - let combined_access = system_meta.component_access_set.combined_access_mut(); + let combined_access = system_meta.component_access_set.combined_access(); if combined_access.has_write(component_id) { panic!( "error[B0002]: NonSendMut<{}> in system {} conflicts with a previous mutable resource access ({0}). Consider removing the duplicate access.", @@ -1085,7 +1219,9 @@ unsafe impl SystemParamState for NonSendMutState { "error[B0002]: NonSendMut<{}> in system {} conflicts with a previous immutable resource access ({0}). Consider removing the duplicate access.", std::any::type_name::(), system_meta.name); } - combined_access.add_write(component_id); + system_meta + .component_access_set + .add_unfiltered_write(component_id); let archetype_component_id = world .get_resource_archetype_component_id(component_id) @@ -1098,18 +1234,14 @@ unsafe impl SystemParamState for NonSendMutState { marker: PhantomData, } } -} - -impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendMutState { - type Item = NonSendMut<'w, T>; #[inline] - unsafe fn get_param( + unsafe fn get_param<'w, 's>( state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { world.validate_non_send_access::(); let (ptr, ticks) = world .get_resource_with_ticks(state.component_id) @@ -1122,11 +1254,7 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendMutState { }); NonSendMut { value: ptr.assert_unique().deref_mut(), - ticks: Ticks { - component_ticks: ticks.deref_mut(), - last_change_tick: system_meta.last_change_tick, - change_tick, - }, + ticks: Ticks::from_tick_cells(ticks, system_meta.last_change_tick, change_tick), } } } @@ -1137,47 +1265,41 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendMutState { pub struct OptionNonSendMutState(NonSendMutState); impl<'a, T: 'static> SystemParam for Option> { - type Fetch = OptionNonSendMutState; + type State = OptionNonSendMutState; } // SAFETY: this impl defers to `NonSendMutState`, which initializes // and validates the correct world access unsafe impl SystemParamState for OptionNonSendMutState { + type Item<'w, 's> = Option>; + fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { Self(NonSendMutState::init(world, system_meta)) } -} - -impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for OptionNonSendMutState { - type Item = Option>; #[inline] - unsafe fn get_param( + unsafe fn get_param<'w, 's>( state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { world.validate_non_send_access::(); world .get_resource_with_ticks(state.0.component_id) .map(|(ptr, ticks)| NonSendMut { value: ptr.assert_unique().deref_mut(), - ticks: Ticks { - component_ticks: ticks.deref_mut(), - last_change_tick: system_meta.last_change_tick, - change_tick, - }, + ticks: Ticks::from_tick_cells(ticks, system_meta.last_change_tick, change_tick), }) } } impl<'a> SystemParam for &'a Archetypes { - type Fetch = ArchetypesState; + type State = ArchetypesState; } // SAFETY: Only reads World archetypes -unsafe impl ReadOnlySystemParamFetch for ArchetypesState {} +unsafe impl<'a> ReadOnlySystemParam for &'a Archetypes {} /// The [`SystemParamState`] of [`Archetypes`]. #[doc(hidden)] @@ -1185,31 +1307,29 @@ pub struct ArchetypesState; // SAFETY: no component value access unsafe impl SystemParamState for ArchetypesState { + type Item<'w, 's> = &'w Archetypes; + fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { Self } -} - -impl<'w, 's> SystemParamFetch<'w, 's> for ArchetypesState { - type Item = &'w Archetypes; #[inline] - unsafe fn get_param( + unsafe fn get_param<'w, 's>( _state: &'s mut Self, _system_meta: &SystemMeta, world: &'w World, _change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { world.archetypes() } } impl<'a> SystemParam for &'a Components { - type Fetch = ComponentsState; + type State = ComponentsState; } // SAFETY: Only reads World components -unsafe impl ReadOnlySystemParamFetch for ComponentsState {} +unsafe impl<'a> ReadOnlySystemParam for &'a Components {} /// The [`SystemParamState`] of [`Components`]. #[doc(hidden)] @@ -1217,31 +1337,29 @@ pub struct ComponentsState; // SAFETY: no component value access unsafe impl SystemParamState for ComponentsState { + type Item<'w, 's> = &'w Components; + fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { Self } -} - -impl<'w, 's> SystemParamFetch<'w, 's> for ComponentsState { - type Item = &'w Components; #[inline] - unsafe fn get_param( + unsafe fn get_param<'w, 's>( _state: &'s mut Self, _system_meta: &SystemMeta, world: &'w World, _change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { world.components() } } impl<'a> SystemParam for &'a Entities { - type Fetch = EntitiesState; + type State = EntitiesState; } // SAFETY: Only reads World entities -unsafe impl ReadOnlySystemParamFetch for EntitiesState {} +unsafe impl<'a> ReadOnlySystemParam for &'a Entities {} /// The [`SystemParamState`] of [`Entities`]. #[doc(hidden)] @@ -1249,31 +1367,29 @@ pub struct EntitiesState; // SAFETY: no component value access unsafe impl SystemParamState for EntitiesState { + type Item<'w, 's> = &'w Entities; + fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { Self } -} - -impl<'w, 's> SystemParamFetch<'w, 's> for EntitiesState { - type Item = &'w Entities; #[inline] - unsafe fn get_param( + unsafe fn get_param<'w, 's>( _state: &'s mut Self, _system_meta: &SystemMeta, world: &'w World, _change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { world.entities() } } impl<'a> SystemParam for &'a Bundles { - type Fetch = BundlesState; + type State = BundlesState; } // SAFETY: Only reads World bundles -unsafe impl ReadOnlySystemParamFetch for BundlesState {} +unsafe impl<'a> ReadOnlySystemParam for &'a Bundles {} /// The [`SystemParamState`] of [`Bundles`]. #[doc(hidden)] @@ -1281,21 +1397,19 @@ pub struct BundlesState; // SAFETY: no component value access unsafe impl SystemParamState for BundlesState { + type Item<'w, 's> = &'w Bundles; + fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { Self } -} - -impl<'w, 's> SystemParamFetch<'w, 's> for BundlesState { - type Item = &'w Bundles; #[inline] - unsafe fn get_param( + unsafe fn get_param<'w, 's>( _state: &'s mut Self, _system_meta: &SystemMeta, world: &'w World, _change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { world.bundles() } } @@ -1330,10 +1444,10 @@ impl SystemChangeTick { } // SAFETY: Only reads internal system state -unsafe impl ReadOnlySystemParamFetch for SystemChangeTickState {} +unsafe impl ReadOnlySystemParam for SystemChangeTick {} impl SystemParam for SystemChangeTick { - type Fetch = SystemChangeTickState; + type State = SystemChangeTickState; } /// The [`SystemParamState`] of [`SystemChangeTick`]. @@ -1342,20 +1456,18 @@ pub struct SystemChangeTickState {} // SAFETY: `SystemParamTickState` doesn't require any world access unsafe impl SystemParamState for SystemChangeTickState { + type Item<'w, 's> = SystemChangeTick; + fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { Self {} } -} - -impl<'w, 's> SystemParamFetch<'w, 's> for SystemChangeTickState { - type Item = SystemChangeTick; - unsafe fn get_param( + unsafe fn get_param<'w, 's>( _state: &'s mut Self, system_meta: &SystemMeta, _world: &'w World, change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { SystemChangeTick { last_change_tick: system_meta.last_change_tick, change_tick, @@ -1411,11 +1523,11 @@ impl<'s> std::fmt::Display for SystemName<'s> { } impl<'s> SystemParam for SystemName<'s> { - type Fetch = SystemNameState; + type State = SystemNameState; } // SAFETY: Only reads internal system state -unsafe impl ReadOnlySystemParamFetch for SystemNameState {} +unsafe impl<'s> ReadOnlySystemParam for SystemName<'s> {} /// The [`SystemParamState`] of [`SystemName`]. #[doc(hidden)] @@ -1425,23 +1537,21 @@ pub struct SystemNameState { // SAFETY: no component value access unsafe impl SystemParamState for SystemNameState { + type Item<'w, 's> = SystemName<'s>; + fn init(_world: &mut World, system_meta: &mut SystemMeta) -> Self { Self { name: system_meta.name.clone(), } } -} - -impl<'w, 's> SystemParamFetch<'w, 's> for SystemNameState { - type Item = SystemName<'s>; #[inline] - unsafe fn get_param( + unsafe fn get_param<'w, 's>( state: &'s mut Self, _system_meta: &SystemMeta, _world: &'w World, _change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { SystemName { name: state.name.as_ref(), } @@ -1451,35 +1561,19 @@ impl<'w, 's> SystemParamFetch<'w, 's> for SystemNameState { macro_rules! impl_system_param_tuple { ($($param: ident),*) => { impl<$($param: SystemParam),*> SystemParam for ($($param,)*) { - type Fetch = ($($param::Fetch,)*); + type State = ($($param::State,)*); } - // SAFETY: tuple consists only of ReadOnlySystemParamFetches - unsafe impl<$($param: ReadOnlySystemParamFetch),*> ReadOnlySystemParamFetch for ($($param,)*) {} - - #[allow(unused_variables)] - #[allow(non_snake_case)] - impl<'w, 's, $($param: SystemParamFetch<'w, 's>),*> SystemParamFetch<'w, 's> for ($($param,)*) { - type Item = ($($param::Item,)*); + // SAFETY: tuple consists only of ReadOnlySystemParams + unsafe impl<$($param: ReadOnlySystemParam),*> ReadOnlySystemParam for ($($param,)*) {} - #[inline] - #[allow(clippy::unused_unit)] - unsafe fn get_param( - state: &'s mut Self, - system_meta: &SystemMeta, - world: &'w World, - change_tick: u32, - ) -> Self::Item { - - let ($($param,)*) = state; - ($($param::get_param($param, system_meta, world, change_tick),)*) - } - } // SAFETY: implementors of each `SystemParamState` in the tuple have validated their impls #[allow(clippy::undocumented_unsafe_blocks)] // false positive by clippy #[allow(non_snake_case)] unsafe impl<$($param: SystemParamState),*> SystemParamState for ($($param,)*) { + type Item<'w, 's> = ($($param::Item::<'w, 's>,)*); + #[inline] fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { (($($param::init(_world, _system_meta),)*)) @@ -1492,9 +1586,22 @@ macro_rules! impl_system_param_tuple { } #[inline] - fn apply(&mut self, _world: &mut World) { + fn apply(&mut self, _system_meta: &SystemMeta, _world: &mut World) { let ($($param,)*) = self; - $($param.apply(_world);)* + $($param.apply(_system_meta, _world);)* + } + + #[inline] + #[allow(clippy::unused_unit)] + unsafe fn get_param<'w, 's>( + state: &'s mut Self, + _system_meta: &SystemMeta, + _world: &'w World, + _change_tick: u32, + ) -> Self::Item<'w, 's> { + + let ($($param,)*) = state; + ($($param::get_param($param, _system_meta, _world, _change_tick),)*) } } }; @@ -1514,7 +1621,7 @@ pub mod lifetimeless { /// A helper for using system parameters in generic contexts /// /// This type is a [`SystemParam`] adapter which always has -/// `Self::Fetch::Item == Self` (ignoring lifetimes for brevity), +/// `Self::State::Item == Self` (ignoring lifetimes for brevity), /// no matter the argument [`SystemParam`] (`P`) (other than /// that `P` must be `'static`) /// @@ -1553,10 +1660,10 @@ pub mod lifetimeless { /// fn do_thing_generically(t: T) {} /// /// #[derive(SystemParam)] -/// struct GenericParam<'w,'s, T: SystemParam> { +/// struct GenericParam<'w, 's, T: SystemParam> { /// field: T, /// #[system_param(ignore)] -/// // Use the lifetimes, as the `SystemParam` derive requires them +/// // Use the lifetimes in this type, or they will be unbound. /// phantom: core::marker::PhantomData<&'w &'s ()> /// } /// # fn check_always_is_system(){ @@ -1592,39 +1699,23 @@ impl<'w, 's, P: SystemParam> StaticSystemParam<'w, 's, P> { pub struct StaticSystemParamState(S, PhantomData P>); // SAFETY: This doesn't add any more reads, and the delegated fetch confirms it -unsafe impl ReadOnlySystemParamFetch - for StaticSystemParamState +unsafe impl<'w, 's, P: ReadOnlySystemParam + 'static> ReadOnlySystemParam + for StaticSystemParam<'w, 's, P> { } impl<'world, 'state, P: SystemParam + 'static> SystemParam for StaticSystemParam<'world, 'state, P> { - type Fetch = StaticSystemParamState; -} - -impl<'world, 'state, S: SystemParamFetch<'world, 'state>, P: SystemParam + 'static> - SystemParamFetch<'world, 'state> for StaticSystemParamState -where - P: SystemParam, -{ - type Item = StaticSystemParam<'world, 'state, P>; - - unsafe fn get_param( - state: &'state mut Self, - system_meta: &SystemMeta, - world: &'world World, - change_tick: u32, - ) -> Self::Item { - // SAFETY: We properly delegate SystemParamState - StaticSystemParam(S::get_param(&mut state.0, system_meta, world, change_tick)) - } + type State = StaticSystemParamState; } // SAFETY: all methods are just delegated to `S`'s `SystemParamState` implementation -unsafe impl SystemParamState +unsafe impl + 'static> SystemParamState for StaticSystemParamState { + type Item<'world, 'state> = StaticSystemParam<'world, 'state, P>; + fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { Self(S::init(world, system_meta), PhantomData) } @@ -1633,14 +1724,24 @@ unsafe impl SystemParamState self.0.new_archetype(archetype, system_meta); } - fn apply(&mut self, world: &mut World) { - self.0.apply(world); + fn apply(&mut self, system_meta: &SystemMeta, world: &mut World) { + self.0.apply(system_meta, world); + } + + unsafe fn get_param<'world, 'state>( + state: &'state mut Self, + system_meta: &SystemMeta, + world: &'world World, + change_tick: u32, + ) -> Self::Item<'world, 'state> { + // SAFETY: We properly delegate SystemParamState + StaticSystemParam(S::get_param(&mut state.0, system_meta, world, change_tick)) } } #[cfg(test)] mod tests { - use super::SystemParam; + use super::*; use crate::{ self as bevy_ecs, // Necessary for the `SystemParam` Derive when used inside `bevy_ecs`. query::{ReadOnlyWorldQuery, WorldQuery}, @@ -1657,4 +1758,55 @@ mod tests { > { _query: Query<'w, 's, Q, F>, } + + #[derive(SystemParam)] + pub struct SpecialRes<'w, T: Resource> { + _res: Res<'w, T>, + } + + #[derive(SystemParam)] + pub struct SpecialLocal<'s, T: FromWorld + Send + 'static> { + _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 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 c9be4096c75b1..dd343461b9e5f 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; @@ -83,6 +98,12 @@ impl> System for PipeSystem< self.system_b.run_unsafe(out, world) } + // needed to make exclusive systems work + fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out { + let out = self.system_a.run(input, world); + self.system_b.run(out, world) + } + fn apply_buffers(&mut self, world: &mut World) { self.system_a.apply_buffers(world); self.system_b.apply_buffers(world); @@ -148,19 +169,15 @@ 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)) } } /// A collection of common adapters for [piping](super::PipeSystem) the result of a system. pub mod adapter { use crate::system::In; + use bevy_utils::tracing; use std::fmt::Debug; /// Converts a regular function into a system adapter. @@ -226,6 +243,142 @@ pub mod adapter { res.unwrap() } + /// System adapter that utilizes the [`bevy_utils::tracing::info!`] macro to print system information. + /// + /// # Examples + /// + /// ``` + /// use bevy_ecs::prelude::*; + /// # + /// # #[derive(StageLabel)] + /// # enum CoreStage { Update }; + /// + /// // Building a new schedule/app... + /// # use bevy_ecs::schedule::SystemStage; + /// # let mut sched = Schedule::default(); sched + /// # .add_stage(CoreStage::Update, SystemStage::parallel()) + /// .add_system_to_stage( + /// CoreStage::Update, + /// // Prints system information. + /// data_pipe_system.pipe(system_adapter::info) + /// ) + /// // ... + /// # ; + /// # let mut world = World::new(); + /// # sched.run(&mut world); + /// + /// // A system that returns a String output. + /// fn data_pipe_system() -> String { + /// "42".to_string() + /// } + /// ``` + pub fn info(In(data): In) { + tracing::info!("{:?}", data); + } + + /// System adapter that utilizes the [`bevy_utils::tracing::debug!`] macro to print the output of a system. + /// + /// # Examples + /// + /// ``` + /// use bevy_ecs::prelude::*; + /// # + /// # #[derive(StageLabel)] + /// # enum CoreStage { Update }; + /// + /// // Building a new schedule/app... + /// # use bevy_ecs::schedule::SystemStage; + /// # let mut sched = Schedule::default(); sched + /// # .add_stage(CoreStage::Update, SystemStage::parallel()) + /// .add_system_to_stage( + /// CoreStage::Update, + /// // Prints debug data from system. + /// parse_message_system.pipe(system_adapter::dbg) + /// ) + /// // ... + /// # ; + /// # let mut world = World::new(); + /// # sched.run(&mut world); + /// + /// // A system that returns a Result output. + /// fn parse_message_system() -> Result { + /// Ok("42".parse()?) + /// } + /// ``` + pub fn dbg(In(data): In) { + tracing::debug!("{:?}", data); + } + + /// System adapter that utilizes the [`bevy_utils::tracing::warn!`] macro to print the output of a system. + /// + /// # Examples + /// + /// ``` + /// use bevy_ecs::prelude::*; + /// # + /// # #[derive(StageLabel)] + /// # enum CoreStage { Update }; + /// + /// // Building a new schedule/app... + /// # use bevy_ecs::schedule::SystemStage; + /// # let mut sched = Schedule::default(); sched + /// # .add_stage(CoreStage::Update, SystemStage::parallel()) + /// .add_system_to_stage( + /// CoreStage::Update, + /// // Prints system warning if system returns an error. + /// warning_pipe_system.pipe(system_adapter::warn) + /// ) + /// // ... + /// # ; + /// # let mut world = World::new(); + /// # sched.run(&mut world); + /// + /// // A system that returns a Result<(), String> output. + /// fn warning_pipe_system() -> Result<(), String> { + /// Err("Got to rusty?".to_string()) + /// } + /// ``` + pub fn warn(In(res): In>) { + if let Err(warn) = res { + tracing::warn!("{:?}", warn); + } + } + + /// System adapter that utilizes the [`bevy_utils::tracing::error!`] macro to print the output of a system. + /// + /// # Examples + /// + /// ``` + /// use bevy_ecs::prelude::*; + /// # + /// # #[derive(StageLabel)] + /// # enum CoreStage { Update }; + /// + /// // Building a new schedule/app... + /// # use bevy_ecs::schedule::SystemStage; + /// # let mut sched = Schedule::default(); sched + /// # .add_stage(CoreStage::Update, SystemStage::parallel()) + /// .add_system_to_stage( + /// CoreStage::Update, + /// // Prints system error if system fails. + /// parse_error_message_system.pipe(system_adapter::error) + /// ) + /// // ... + /// # ; + /// # let mut world = World::new(); + /// # sched.run(&mut world); + /// + /// // A system that returns a Result<())> output. + /// fn parse_error_message_system() -> Result<(), String> { + /// Err("Some error".to_owned()) + /// } + /// ``` + pub fn error(In(res): In>) { + if let Err(error) = res { + tracing::error!("{:?}", error); + } + } + /// System adapter that ignores the output of the previous system in a pipe. /// This is useful for fallible systems that should simply return early in case of an `Err`/`None`. /// diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index c9f9321d5a7b3..251945d711b09 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -2,14 +2,17 @@ use crate::{ archetype::{Archetype, ArchetypeId, Archetypes}, bundle::{Bundle, BundleInfo}, change_detection::{MutUntyped, Ticks}, - component::{Component, ComponentId, ComponentTicks, Components, StorageType}, + component::{ + Component, ComponentId, ComponentStorage, ComponentTicks, Components, StorageType, + TickCells, + }, entity::{Entities, Entity, EntityLocation}, - storage::{SparseSet, Storages}, + storage::{Column, ComponentSparseSet, SparseSet, Storages}, world::{Mut, World}, }; -use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; +use bevy_ptr::{OwningPtr, Ptr}; use bevy_utils::tracing::debug; -use std::{any::TypeId, cell::UnsafeCell}; +use std::any::TypeId; /// A read-only reference to a particular [`Entity`] and all of its components #[derive(Copy, Clone)] @@ -67,21 +70,62 @@ impl<'w> EntityRef<'w> { #[inline] pub fn get(&self) -> Option<&'w T> { - // SAFETY: entity location is valid and returned component is of type T + // SAFETY: + // - entity location and entity is valid + // - returned component is of type T + // - the storage type provided is correct for T unsafe { - get_component_with_type(self.world, TypeId::of::(), self.entity, self.location) - .map(|value| value.deref::()) + get_component_with_type( + self.world, + TypeId::of::(), + T::Storage::STORAGE_TYPE, + self.entity, + self.location, + ) + .map(|value| value.deref::()) } } /// Retrieves the change ticks for the given component. This can be useful for implementing change /// detection in custom runtimes. #[inline] - pub fn get_change_ticks(&self) -> Option<&'w ComponentTicks> { - // SAFETY: entity location is valid + pub fn get_change_ticks(&self) -> Option { + // SAFETY: + // - entity location and entity is valid + // - the storage type provided is correct for T unsafe { - get_ticks_with_type(self.world, TypeId::of::(), self.entity, self.location) - .map(|ticks| ticks.deref()) + get_ticks_with_type( + self.world, + TypeId::of::(), + T::Storage::STORAGE_TYPE, + self.entity, + self.location, + ) + } + } + + /// Retrieves the change ticks for the given [`ComponentId`]. This can be useful for implementing change + /// detection in custom runtimes. + /// + /// **You should prefer to use the typed API [`EntityRef::get_change_ticks`] where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + #[inline] + pub fn get_change_ticks_by_id(&self, component_id: ComponentId) -> Option { + if !self.contains_id(component_id) { + return None; + } + + let info = self.world.components().get_info(component_id)?; + // SAFETY: Entity location is valid and component_id exists. + unsafe { + get_ticks( + self.world, + component_id, + info.storage_type(), + self.entity, + self.location, + ) } } @@ -101,15 +145,24 @@ impl<'w> EntityRef<'w> { last_change_tick: u32, change_tick: u32, ) -> Option> { - get_component_and_ticks_with_type(self.world, TypeId::of::(), self.entity, self.location) - .map(|(value, ticks)| Mut { - value: value.assert_unique().deref_mut::(), - ticks: Ticks { - component_ticks: ticks.deref_mut(), - last_change_tick, - change_tick, - }, - }) + // SAFETY: + // - entity location and entity is valid + // - returned component is of type T + // - the storage type provided is correct for T + get_component_and_ticks_with_type( + self.world, + TypeId::of::(), + T::Storage::STORAGE_TYPE, + self.entity, + 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), + }) } } @@ -124,9 +177,20 @@ impl<'w> EntityRef<'w> { /// which is only valid while the `'w` borrow of the lifetime is active. #[inline] pub fn get_by_id(&self, component_id: ComponentId) -> Option> { - self.world.components().get_info(component_id)?; - // SAFETY: entity_location is valid, component_id is valid as checked by the line above - unsafe { get_component(self.world, component_id, self.entity, self.location) } + let info = self.world.components().get_info(component_id)?; + // SAFETY: + // - entity_location is valid, + // - component_id is valid as checked by the line above + // - the storage type is accurate as checked by the fetched ComponentInfo + unsafe { + get_component( + self.world, + component_id, + info.storage_type(), + self.entity, + self.location, + ) + } } } @@ -192,10 +256,20 @@ impl<'w> EntityMut<'w> { #[inline] pub fn get(&self) -> Option<&'_ T> { - // SAFETY: lifetimes enforce correct usage of returned borrow + // SAFETY: + // - lifetimes enforce correct usage of returned borrow + // - entity location and entity is valid + // - returned component is of type T + // - the storage type provided is correct for T unsafe { - get_component_with_type(self.world, TypeId::of::(), self.entity, self.location) - .map(|value| value.deref::()) + get_component_with_type( + self.world, + TypeId::of::(), + T::Storage::STORAGE_TYPE, + self.entity, + self.location, + ) + .map(|value| value.deref::()) } } @@ -208,11 +282,43 @@ impl<'w> EntityMut<'w> { /// Retrieves the change ticks for the given component. This can be useful for implementing change /// detection in custom runtimes. #[inline] - pub fn get_change_ticks(&self) -> Option<&ComponentTicks> { - // SAFETY: entity location is valid + pub fn get_change_ticks(&self) -> Option { + // SAFETY: + // - entity location and entity is valid + // - the storage type provided is correct for T unsafe { - get_ticks_with_type(self.world, TypeId::of::(), self.entity, self.location) - .map(|ticks| ticks.deref()) + get_ticks_with_type( + self.world, + TypeId::of::(), + T::Storage::STORAGE_TYPE, + self.entity, + self.location, + ) + } + } + + /// Retrieves the change ticks for the given [`ComponentId`]. This can be useful for implementing change + /// detection in custom runtimes. + /// + /// **You should prefer to use the typed API [`EntityMut::get_change_ticks`] where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + #[inline] + pub fn get_change_ticks_by_id(&self, component_id: ComponentId) -> Option { + if !self.contains_id(component_id) { + return None; + } + + let info = self.world.components().get_info(component_id)?; + // SAFETY: Entity location is valid and component_id exists. + unsafe { + get_ticks( + self.world, + component_id, + info.storage_type(), + self.entity, + self.location, + ) } } @@ -228,23 +334,25 @@ impl<'w> EntityMut<'w> { /// operation on this world (non-exhaustive list). #[inline] pub unsafe fn get_unchecked_mut(&self) -> Option> { - get_component_and_ticks_with_type(self.world, TypeId::of::(), self.entity, self.location) - .map(|(value, ticks)| Mut { - value: value.assert_unique().deref_mut::(), - ticks: Ticks { - component_ticks: ticks.deref_mut(), - last_change_tick: self.world.last_change_tick(), - change_tick: self.world.read_change_tick(), - }, - }) - } - - #[deprecated( - since = "0.9.0", - note = "Use `insert` instead, which now accepts bundles, components, and tuples of bundles and components." - )] - pub fn insert_bundle(&mut self, bundle: T) -> &mut Self { - self.insert(bundle) + // SAFETY: + // - entity location and entity is valid + // - returned component is of type T + // - the storage type provided is correct for T + get_component_and_ticks_with_type( + self.world, + TypeId::of::(), + T::Storage::STORAGE_TYPE, + self.entity, + self.location, + ) + .map(|(value, ticks)| Mut { + value: value.assert_unique().deref_mut::(), + ticks: Ticks::from_tick_cells( + ticks, + self.world.last_change_tick(), + self.world.read_change_tick(), + ), + }) } /// Adds a [`Bundle`] of components to the entity. @@ -266,20 +374,12 @@ impl<'w> EntityMut<'w> { ); // SAFETY: location matches current entity. `T` matches `bundle_info` unsafe { - self.location = bundle_inserter.insert(self.entity, self.location.index, bundle); + self.location = bundle_inserter.insert(self.entity, self.location, bundle); } self } - #[deprecated( - since = "0.9.0", - note = "Use `remove` instead, which now accepts bundles, components, and tuples of bundles and components." - )] - pub fn remove_bundle(&mut self) -> Option { - self.remove::() - } - // TODO: move to BundleInfo /// Removes a [`Bundle`] of components from the entity and returns the bundle. /// @@ -310,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 @@ -322,7 +421,6 @@ impl<'w> EntityMut<'w> { take_component( components, storages, - old_archetype, removed_components, component_id, entity, @@ -368,9 +466,9 @@ impl<'w> EntityMut<'w> { new_archetype_id: ArchetypeId, ) { let old_archetype = &mut archetypes[old_archetype_id]; - let remove_result = old_archetype.swap_remove(old_location.index); + let remove_result = old_archetype.swap_remove(old_location.archetype_row); if let Some(swapped_entity) = remove_result.swapped_entity { - entities.meta[swapped_entity.index as usize].location = old_location; + entities.set(swapped_entity.index(), old_location); } let old_table_row = remove_result.table_row; let old_table_id = old_archetype.table_id(); @@ -397,22 +495,15 @@ impl<'w> EntityMut<'w> { if let Some(swapped_entity) = move_result.swapped_entity { let swapped_location = entities.get(swapped_entity).unwrap(); archetypes[swapped_location.archetype_id] - .set_entity_table_row(swapped_location.index, old_table_row); + .set_entity_table_row(swapped_location.archetype_row, old_table_row); } new_location }; *self_location = new_location; - entities.meta[entity.index as usize].location = new_location; - } - - #[deprecated( - since = "0.9.0", - note = "Use `remove_intersection` instead, which now accepts bundles, components, and tuples of bundles and components." - )] - pub fn remove_bundle_intersection(&mut self) { - self.remove_intersection::(); + // SAFETY: The entity is valid and has been moved to the new location already. + entities.set(entity.index(), new_location); } // TODO: move to BundleInfo @@ -498,14 +589,18 @@ impl<'w> EntityMut<'w> { .get_or_insert_with(component_id, Vec::new); removed_components.push(self.entity); } - let remove_result = archetype.swap_remove(location.index); + let remove_result = archetype.swap_remove(location.archetype_row); if let Some(swapped_entity) = remove_result.swapped_entity { - world.entities.meta[swapped_entity.index as usize].location = location; + // SAFETY: swapped_entity is valid and the swapped entity's components are + // moved to the new location immediately after. + unsafe { + world.entities.set(swapped_entity.index(), location); + } } table_row = remove_result.table_row; for component_id in archetype.sparse_set_components() { - let sparse_set = world.storages.sparse_sets.get_mut(*component_id).unwrap(); + let sparse_set = world.storages.sparse_sets.get_mut(component_id).unwrap(); sparse_set.remove(self.entity); } // SAFETY: table rows stored in archetypes always exist @@ -517,7 +612,7 @@ impl<'w> EntityMut<'w> { if let Some(moved_entity) = moved_entity { let moved_location = world.entities.get(moved_entity).unwrap(); world.archetypes[moved_location.archetype_id] - .set_entity_table_row(moved_location.index, table_row); + .set_entity_table_row(moved_location.archetype_row, table_row); } } @@ -570,9 +665,20 @@ impl<'w> EntityMut<'w> { /// which is only valid while the [`EntityMut`] is alive. #[inline] pub fn get_by_id(&self, component_id: ComponentId) -> Option> { - self.world.components().get_info(component_id)?; - // SAFETY: entity_location is valid, component_id is valid as checked by the line above - unsafe { get_component(self.world, component_id, self.entity, self.location) } + let info = self.world.components().get_info(component_id)?; + // SAFETY: + // - entity_location is valid + // - component_id is valid as checked by the line above + // - the storage type is accurate as checked by the fetched ComponentInfo + unsafe { + get_component( + self.world, + component_id, + info.storage_type(), + self.entity, + self.location, + ) + } } /// Gets a [`MutUntyped`] of the component of the given [`ComponentId`] from the entity. @@ -591,36 +697,43 @@ impl<'w> EntityMut<'w> { } } +#[inline] +fn fetch_table( + world: &World, + location: EntityLocation, + component_id: ComponentId, +) -> Option<&Column> { + world.storages.tables[location.table_id].get_column(component_id) +} + +#[inline] +fn fetch_sparse_set(world: &World, component_id: ComponentId) -> Option<&ComponentSparseSet> { + world.storages.sparse_sets.get(component_id) +} + // TODO: move to Storages? /// 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] pub(crate) unsafe fn get_component( world: &World, component_id: ComponentId, + storage_type: StorageType, entity: Entity, location: EntityLocation, ) -> Option> { - let archetype = &world.archetypes[location.archetype_id]; - // SAFETY: component_id exists and is therefore valid - let component_info = world.components.get_info_unchecked(component_id); - match component_info.storage_type() { + match storage_type { StorageType::Table => { - let table = &world.storages.tables[archetype.table_id()]; - let components = table.get_column(component_id)?; - let table_row = archetype.entity_table_row(location.index); + 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 => world - .storages - .sparse_sets - .get(component_id) - .and_then(|sparse_set| sparse_set.get(entity)), + StorageType::SparseSet => fetch_sparse_set(world, component_id)?.get(entity), } } @@ -628,57 +741,53 @@ 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 -/// Caller must ensure that `component_id` is valid +/// - Caller must ensure that `component_id` is valid +/// - `location` must be within bounds of the given archetype and `entity` must exist inside +/// the archetype +/// - `storage_type` must accurately reflect where the components for `component_id` are stored. #[inline] unsafe fn get_component_and_ticks( world: &World, component_id: ComponentId, + storage_type: StorageType, entity: Entity, location: EntityLocation, -) -> Option<(Ptr<'_>, &UnsafeCell)> { - let archetype = &world.archetypes[location.archetype_id]; - let component_info = world.components.get_info_unchecked(component_id); - match component_info.storage_type() { +) -> Option<(Ptr<'_>, TickCells<'_>)> { + match storage_type { StorageType::Table => { - let table = &world.storages.tables[archetype.table_id()]; - let components = table.get_column(component_id)?; - let table_row = archetype.entity_table_row(location.index); + 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_ticks_unchecked(table_row), + components.get_data_unchecked(location.table_row), + TickCells { + added: components.get_added_ticks_unchecked(location.table_row), + changed: components.get_changed_ticks_unchecked(location.table_row), + }, )) } - StorageType::SparseSet => world - .storages - .sparse_sets - .get(component_id) - .and_then(|sparse_set| sparse_set.get_with_ticks(entity)), + StorageType::SparseSet => fetch_sparse_set(world, component_id)?.get_with_ticks(entity), } } -#[inline] +/// # Safety +/// - `entity_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. unsafe fn get_ticks( world: &World, component_id: ComponentId, + storage_type: StorageType, entity: Entity, location: EntityLocation, -) -> Option<&UnsafeCell> { - let archetype = &world.archetypes[location.archetype_id]; - let component_info = world.components.get_info_unchecked(component_id); - match component_info.storage_type() { +) -> Option { + match storage_type { StorageType::Table => { - let table = &world.storages.tables[archetype.table_id()]; - let components = table.get_column(component_id)?; - let table_row = archetype.entity_table_row(location.index); + 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 => world - .storages - .sparse_sets - .get(component_id) - .and_then(|sparse_set| sparse_set.get_ticks(entity)), + StorageType::SparseSet => fetch_sparse_set(world, component_id)?.get_ticks(entity), } } @@ -690,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, @@ -708,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.index); // 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 @@ -727,41 +837,71 @@ unsafe fn take_component<'a>( /// Get a raw pointer to a particular [`Component`] by [`TypeId`] on a particular [`Entity`] in the provided [`World`]. /// /// # Safety -/// `entity_location` must be within bounds of an archetype that exists. +/// - `entity_location` must be within bounds of the given archetype and `entity` must exist inside +/// the archetype +/// - `type_id` must be correspond to a type that implements [`Component`] +/// - `storage_type` must accurately reflect where the components for `component_id` are stored. +#[inline] unsafe fn get_component_with_type( world: &World, type_id: TypeId, + storage_type: StorageType, entity: Entity, location: EntityLocation, ) -> Option> { - let component_id = world.components.get_id(type_id)?; - get_component(world, component_id, entity, location) + get_component( + world, + world.components.get_id(type_id)?, + storage_type, + entity, + location, + ) } /// Get a raw pointer to the [`ComponentTicks`] of a particular [`Component`] by [`TypeId`] on a particular [`Entity`] in the provided [`World`]. /// /// # Safety -/// `entity_location` must be within bounds of an archetype that exists. -pub(crate) unsafe fn get_component_and_ticks_with_type( +/// - `entity_location` must be within bounds of the given archetype and `entity` must exist inside +/// the archetype +/// - `type_id` must be correspond to a type that implements [`Component`] +/// - `storage_type` must accurately reflect where the components for `component_id` are stored. +#[inline] +unsafe fn get_component_and_ticks_with_type( world: &World, type_id: TypeId, + storage_type: StorageType, entity: Entity, location: EntityLocation, -) -> Option<(Ptr<'_>, &UnsafeCell)> { - let component_id = world.components.get_id(type_id)?; - get_component_and_ticks(world, component_id, entity, location) +) -> Option<(Ptr<'_>, TickCells<'_>)> { + get_component_and_ticks( + world, + world.components.get_id(type_id)?, + storage_type, + entity, + location, + ) } /// # Safety -/// `entity_location` must be within bounds of an archetype that exists. -pub(crate) unsafe fn get_ticks_with_type( +/// - `entity_location` must be within bounds of the given archetype and `entity` must exist inside +/// the archetype +/// - `type_id` must be correspond to a type that implements [`Component`] +/// - `storage_type` must accurately reflect where the components for `component_id` are stored. +#[inline] +unsafe fn get_ticks_with_type( world: &World, type_id: TypeId, + storage_type: StorageType, entity: Entity, location: EntityLocation, -) -> Option<&UnsafeCell> { - let component_id = world.components.get_id(type_id)?; - get_ticks(world, component_id, entity, location) +) -> Option { + get_ticks( + world, + world.components.get_id(type_id)?, + storage_type, + entity, + location, + ) } fn contains_component_with_type(world: &World, type_id: TypeId, location: EntityLocation) -> bool { @@ -843,8 +983,8 @@ unsafe fn remove_bundle_from_archetype( // components are already sorted removed_table_components.sort(); removed_sparse_set_components.sort(); - next_table_components = current_archetype.table_components().to_vec(); - next_sparse_set_components = current_archetype.sparse_set_components().to_vec(); + next_table_components = current_archetype.table_components().collect(); + next_sparse_set_components = current_archetype.sparse_set_components().collect(); sorted_remove(&mut next_table_components, &removed_table_components); sorted_remove( &mut next_sparse_set_components, @@ -908,16 +1048,17 @@ pub(crate) unsafe fn get_mut( // T let change_tick = world.change_tick(); let last_change_tick = world.last_change_tick(); - get_component_and_ticks_with_type(world, TypeId::of::(), entity, location).map( - |(value, ticks)| Mut { - value: value.assert_unique().deref_mut::(), - ticks: Ticks { - component_ticks: ticks.deref_mut(), - last_change_tick, - change_tick, - }, - }, + get_component_and_ticks_with_type( + world, + TypeId::of::(), + T::Storage::STORAGE_TYPE, + entity, + location, ) + .map(|(value, ticks)| Mut { + value: value.assert_unique().deref_mut::(), + ticks: Ticks::from_tick_cells(ticks, last_change_tick, change_tick), + }) } // SAFETY: EntityLocation must be valid, component_id must be valid @@ -928,17 +1069,15 @@ pub(crate) unsafe fn get_mut_by_id( location: EntityLocation, component_id: ComponentId, ) -> Option { + let change_tick = world.change_tick(); + let info = world.components.get_info_unchecked(component_id); // SAFETY: world access is unique, entity location and component_id required to be valid - get_component_and_ticks(world, component_id, entity, location).map(|(value, ticks)| { - MutUntyped { + get_component_and_ticks(world, component_id, info.storage_type(), entity, location).map( + |(value, ticks)| MutUntyped { value: value.assert_unique(), - ticks: Ticks { - component_ticks: ticks.deref_mut(), - last_change_tick: world.last_change_tick(), - change_tick: world.read_change_tick(), - }, - } - }) + ticks: Ticks::from_tick_cells(ticks, world.last_change_tick(), change_tick), + }, + ) } #[cfg(test)] diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 1aab56a9f4ae7..e4569f6be5220 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -8,22 +8,22 @@ pub use spawn_batch::*; pub use world_cell::*; use crate::{ - archetype::{ArchetypeComponentId, ArchetypeId, Archetypes}, + archetype::{ArchetypeComponentId, ArchetypeId, ArchetypeRow, Archetypes}, bundle::{Bundle, BundleInserter, BundleSpawner, Bundles}, change_detection::{MutUntyped, Ticks}, component::{ - Component, ComponentDescriptor, ComponentId, ComponentInfo, ComponentTicks, Components, + Component, ComponentDescriptor, ComponentId, ComponentInfo, Components, TickCells, }, - entity::{AllocAtWithoutReplacement, Entities, Entity}, + entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation}, + event::{Event, Events}, query::{QueryState, ReadOnlyWorldQuery, WorldQuery}, storage::{ResourceData, SparseSet, Storages}, system::Resource, }; -use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; +use bevy_ptr::{OwningPtr, Ptr}; use bevy_utils::tracing::warn; use std::{ any::TypeId, - cell::UnsafeCell, fmt, sync::atomic::{AtomicU32, Ordering}, }; @@ -69,9 +69,9 @@ impl Default for World { fn default() -> Self { Self { id: WorldId::new().expect("More `bevy` `World`s have been created than is supported"), - entities: Default::default(), + entities: Entities::new(), components: Default::default(), - archetypes: Default::default(), + archetypes: Archetypes::new(), storages: Default::default(), bundles: Default::default(), removed_components: Default::default(), @@ -324,11 +324,22 @@ impl World { /// /// This is useful in contexts where you only have read-only access to the [`World`]. #[inline] - pub fn iter_entities(&self) -> impl Iterator + '_ { - self.archetypes - .iter() - .flat_map(|archetype| archetype.entities().iter()) - .map(|archetype_entity| archetype_entity.entity) + pub fn iter_entities(&self) -> impl Iterator> + '_ { + self.archetypes.iter().flat_map(|archetype| { + archetype + .entities() + .iter() + .enumerate() + .map(|(archetype_row, archetype_entity)| { + 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) + }) + }) } /// Retrieves an [`EntityMut`] that exposes read and write operations for the given `entity`. @@ -481,10 +492,7 @@ impl World { // empty let location = archetype.allocate(entity, table_row); // SAFETY: entity index was just allocated - self.entities - .meta - .get_unchecked_mut(entity.index() as usize) - .location = location; + self.entities.set(entity.index(), location); EntityMut::new(self, entity, location) } @@ -590,7 +598,45 @@ impl World { } } - /// Clears component tracker state + /// Clears the internal component tracker state. + /// + /// The world maintains some internal state about changed and removed components. This state + /// is used by [`RemovedComponents`] to provide access to the entities that had a specific type + /// of component removed since last tick. + /// + /// The state is also used for change detection when accessing components and resources outside + /// of a system, for example via [`World::get_mut()`] or [`World::get_resource_mut()`]. + /// + /// By clearing this internal state, the world "forgets" about those changes, allowing a new round + /// of detection to be recorded. + /// + /// When using `bevy_ecs` as part of the full Bevy engine, this method is added as a system to the + /// main app, to run during the `CoreStage::Last`, so you don't need to call it manually. When using + /// `bevy_ecs` as a separate standalone crate however, you need to call this manually. + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # #[derive(Component, Default)] + /// # struct Transform; + /// // a whole new world + /// let mut world = World::new(); + /// + /// // you changed it + /// let entity = world.spawn(Transform::default()).id(); + /// + /// // change is detected + /// let transform = world.get_mut::(entity).unwrap(); + /// assert!(transform.is_changed()); + /// + /// // update the last change tick + /// world.clear_trackers(); + /// + /// // change is no longer detected + /// let transform = world.get_mut::(entity).unwrap(); + /// assert!(!transform.is_changed()); + /// ``` + /// + /// [`RemovedComponents`]: crate::system::RemovedComponents pub fn clear_trackers(&mut self) { for entities in self.removed_components.values_mut() { entities.clear(); @@ -890,7 +936,7 @@ impl World { #[inline] pub fn get_resource(&self) -> Option<&R> { let component_id = self.components.get_resource_id(TypeId::of::())?; - // SAFETY: unique world access + // SAFETY: `component_id` was obtained from the type ID of `R`. unsafe { self.get_resource_with_id(component_id) } } @@ -1001,7 +1047,7 @@ impl World { pub(crate) fn get_resource_with_ticks( &self, component_id: ComponentId, - ) -> Option<(Ptr<'_>, &UnsafeCell)> { + ) -> Option<(Ptr<'_>, TickCells<'_>)> { self.storages.resources.get(component_id)?.get_with_ticks() } @@ -1091,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.index, bundle) }; + unsafe { inserter.insert(entity, location, bundle) }; } _ => { let mut inserter = bundle_info.get_bundle_inserter( @@ -1103,7 +1149,7 @@ impl World { change_tick, ); // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter - unsafe { inserter.insert(entity, location.index, bundle) }; + unsafe { inserter.insert(entity, location, bundle) }; spawn_or_insert = SpawnOrInsert::Insert(inserter, location.archetype_id); } @@ -1194,7 +1240,8 @@ impl World { let value_mut = Mut { value: &mut value, ticks: Ticks { - component_ticks: &mut ticks, + added: &mut ticks.added, + changed: &mut ticks.changed, last_change_tick, change_tick, }, @@ -1224,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 ", @@ -1273,11 +1320,7 @@ impl World { let (ptr, ticks) = self.get_resource_with_ticks(component_id)?; Some(Mut { value: ptr.assert_unique().deref_mut(), - ticks: Ticks { - component_ticks: ticks.deref_mut(), - last_change_tick: self.last_change_tick(), - change_tick: self.read_change_tick(), - }, + ticks: Ticks::from_tick_cells(ticks, self.last_change_tick(), self.read_change_tick()), }) } @@ -1394,11 +1437,19 @@ impl World { self.change_tick.fetch_add(1, Ordering::AcqRel) } + /// Reads the current change tick of this world. + /// + /// If you have exclusive (`&mut`) access to the world, consider using [`change_tick()`](Self::change_tick), + /// which is more efficient since it does not require atomic synchronization. #[inline] pub fn read_change_tick(&self) -> u32 { self.change_tick.load(Ordering::Acquire) } + /// Reads the current change tick of this world. + /// + /// This does the same thing as [`read_change_tick()`](Self::read_change_tick), only this method + /// is more efficient since it does not require atomic synchronization. #[inline] pub fn change_tick(&mut self) -> u32 { *self.change_tick.get_mut() @@ -1455,17 +1506,14 @@ impl World { self.validate_non_send_access_untyped(info.name()); } + let change_tick = self.change_tick(); + let (ptr, ticks) = self.get_resource_with_ticks(component_id)?; - // SAFE: This function has exclusive access to the world so nothing aliases `ticks`. - let ticks = Ticks { - // SAFETY: - // - index is in-bounds because the column is initialized and non-empty - // - no other reference to the ticks of the same row can exist at the same time - component_ticks: unsafe { ticks.deref_mut() }, - last_change_tick: self.last_change_tick(), - change_tick: self.read_change_tick(), - }; + // SAFETY: This function has exclusive access to the world so nothing aliases `ticks`. + // - index is in-bounds because the column is initialized and non-empty + // - no other reference to the ticks of the same row can exist at the same time + let ticks = unsafe { Ticks::from_tick_cells(ticks, self.last_change_tick(), change_tick) }; Some(MutUntyped { // SAFETY: This function has exclusive access to the world so nothing aliases `ptr`. @@ -1500,12 +1548,16 @@ impl World { /// use this in cases where the actual types are not known at compile time.** #[inline] pub fn get_by_id(&self, entity: Entity, component_id: ComponentId) -> Option> { - self.components().get_info(component_id)?; - // SAFETY: entity_location is valid, component_id is valid as checked by the line above + let info = self.components().get_info(component_id)?; + // SAFETY: + // - entity_location is valid + // - component_id is valid as checked by the line above + // - the storage type is accurate as checked by the fetched ComponentInfo unsafe { get_component( self, component_id, + info.storage_type(), entity, self.get_entity(entity)?.location(), ) @@ -1880,7 +1932,7 @@ mod tests { let iterate_and_count_entities = |world: &World, entity_counters: &mut HashMap<_, _>| { entity_counters.clear(); for entity in world.iter_entities() { - let counter = entity_counters.entry(entity).or_insert(0); + let counter = entity_counters.entry(entity.id()).or_insert(0); *counter += 1; } }; diff --git a/crates/bevy_ecs/src/world/world_cell.rs b/crates/bevy_ecs/src/world/world_cell.rs index 39770890e2b0d..5f0fb5aaa5a74 100644 --- a/crates/bevy_ecs/src/world/world_cell.rs +++ b/crates/bevy_ecs/src/world/world_cell.rs @@ -88,21 +88,22 @@ pub struct WorldBorrow<'w, T> { } impl<'w, T> WorldBorrow<'w, T> { - fn new( - value: &'w T, + fn try_new( + value: impl FnOnce() -> Option<&'w T>, archetype_component_id: ArchetypeComponentId, access: Rc>, - ) -> Self { + ) -> Option { assert!( access.borrow_mut().read(archetype_component_id), "Attempted to immutably access {}, but it is already mutably borrowed", std::any::type_name::(), ); - Self { + let value = value()?; + Some(Self { value, archetype_component_id, access, - } + }) } } @@ -129,21 +130,22 @@ pub struct WorldBorrowMut<'w, T> { } impl<'w, T> WorldBorrowMut<'w, T> { - fn new( - value: Mut<'w, T>, + fn try_new( + value: impl FnOnce() -> Option>, archetype_component_id: ArchetypeComponentId, access: Rc>, - ) -> Self { + ) -> Option { assert!( access.borrow_mut().write(archetype_component_id), "Attempted to mutably access {}, but it is already mutably borrowed", std::any::type_name::(), ); - Self { + let value = value()?; + Some(Self { value, archetype_component_id, access, - } + }) } } @@ -189,12 +191,12 @@ impl<'w> WorldCell<'w> { let archetype_component_id = self .world .get_resource_archetype_component_id(component_id)?; - Some(WorldBorrow::new( + WorldBorrow::try_new( // SAFETY: ComponentId matches TypeId - unsafe { self.world.get_resource_with_id(component_id)? }, + || unsafe { self.world.get_resource_with_id(component_id) }, archetype_component_id, self.access.clone(), - )) + ) } /// Gets a reference to the resource of the given type @@ -222,15 +224,12 @@ impl<'w> WorldCell<'w> { let archetype_component_id = self .world .get_resource_archetype_component_id(component_id)?; - Some(WorldBorrowMut::new( + WorldBorrowMut::try_new( // SAFETY: ComponentId matches TypeId and access is checked by WorldBorrowMut - unsafe { - self.world - .get_resource_unchecked_mut_with_id(component_id)? - }, + || unsafe { self.world.get_resource_unchecked_mut_with_id(component_id) }, archetype_component_id, self.access.clone(), - )) + ) } /// Gets a mutable reference to the resource of the given type @@ -258,12 +257,12 @@ impl<'w> WorldCell<'w> { let archetype_component_id = self .world .get_resource_archetype_component_id(component_id)?; - Some(WorldBorrow::new( + WorldBorrow::try_new( // SAFETY: ComponentId matches TypeId - unsafe { self.world.get_non_send_with_id(component_id)? }, + || unsafe { self.world.get_non_send_with_id(component_id) }, archetype_component_id, self.access.clone(), - )) + ) } /// Gets an immutable reference to the non-send resource of the given type, if it exists. @@ -291,15 +290,12 @@ impl<'w> WorldCell<'w> { let archetype_component_id = self .world .get_resource_archetype_component_id(component_id)?; - Some(WorldBorrowMut::new( + WorldBorrowMut::try_new( // SAFETY: ComponentId matches TypeId and access is checked by WorldBorrowMut - unsafe { - self.world - .get_non_send_unchecked_mut_with_id(component_id)? - }, + || unsafe { self.world.get_non_send_unchecked_mut_with_id(component_id) }, archetype_component_id, self.access.clone(), - )) + ) } /// Gets a mutable reference to the non-send resource of the given type, if it exists. diff --git a/crates/bevy_ecs_compile_fail_tests/tests/ui/system_param_derive_readonly.rs b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_param_derive_readonly.rs index 048a1557693f8..7e2baeda2535c 100644 --- a/crates/bevy_ecs_compile_fail_tests/tests/ui/system_param_derive_readonly.rs +++ b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_param_derive_readonly.rs @@ -1,5 +1,5 @@ use bevy_ecs::prelude::*; -use bevy_ecs::system::{ReadOnlySystemParamFetch, SystemParam, SystemState}; +use bevy_ecs::system::{ReadOnlySystemParam, SystemParam, SystemState}; #[derive(Component)] struct Foo; @@ -18,8 +18,8 @@ fn main() { assert_readonly::(); } -fn assert_readonly() +fn assert_readonly

() where -

::Fetch: ReadOnlySystemParamFetch, + P: ReadOnlySystemParam, { } diff --git a/crates/bevy_ecs_compile_fail_tests/tests/ui/system_param_derive_readonly.stderr b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_param_derive_readonly.stderr index 0878af9e4bdd9..e1cee3b0fd2fe 100644 --- a/crates/bevy_ecs_compile_fail_tests/tests/ui/system_param_derive_readonly.stderr +++ b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_param_derive_readonly.stderr @@ -1,8 +1,8 @@ warning: unused import: `SystemState` - --> tests/ui/system_param_derive_readonly.rs:2:63 + --> tests/ui/system_param_derive_readonly.rs:2:58 | -2 | use bevy_ecs::system::{ReadOnlySystemParamFetch, SystemParam, SystemState}; - | ^^^^^^^^^^^ +2 | use bevy_ecs::system::{ReadOnlySystemParam, SystemParam, SystemState}; + | ^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default @@ -23,14 +23,14 @@ error[E0277]: the trait bound `&'static mut Foo: ReadOnlyWorldQuery` is not sati (F0, F1, F2, F3, F4, F5, F6) and $N others = note: `ReadOnlyWorldQuery` is implemented for `&'static Foo`, but not for `&'static mut Foo` - = note: required for `QueryState<&'static mut Foo>` to implement `ReadOnlySystemParamFetch` - = note: 2 redundant requirements hidden - = note: required for `FetchState<(QueryState<&'static mut Foo>,)>` to implement `ReadOnlySystemParamFetch` + = note: required for `bevy_ecs::system::Query<'_, '_, &'static mut Foo>` to implement `ReadOnlySystemParam` + = note: 1 redundant requirement hidden + = note: required for `Mutable<'_, '_>` to implement `ReadOnlySystemParam` note: required by a bound in `assert_readonly` - --> tests/ui/system_param_derive_readonly.rs:23:32 + --> tests/ui/system_param_derive_readonly.rs:23:8 | -21 | fn assert_readonly() +21 | fn assert_readonly

() | --------------- required by a bound in this 22 | where -23 |

::Fetch: ReadOnlySystemParamFetch, - | ^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_readonly` +23 | P: ReadOnlySystemParam, + | ^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_readonly` 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 a78f5a85ec849..f2aa5dc1a5566 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_gilrs/Cargo.toml b/crates/bevy_gilrs/Cargo.toml index 4623ee5050133..b783420252906 100644 --- a/crates/bevy_gilrs/Cargo.toml +++ b/crates/bevy_gilrs/Cargo.toml @@ -16,4 +16,4 @@ bevy_input = { path = "../bevy_input", version = "0.9.0" } bevy_utils = { path = "../bevy_utils", version = "0.9.0" } # other -gilrs = "0.9.0" +gilrs = "0.10.1" diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 5459c37d5dd9a..81d793993ac7c 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( @@ -669,7 +669,7 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle< load_context.set_labeled_asset( &material_label, LoadedAsset::new(StandardMaterial { - base_color: Color::rgba(color[0], color[1], color[2], color[3]), + base_color: Color::rgba_linear(color[0], color[1], color[2], color[3]), base_color_texture, perceptual_roughness: pbr.roughness_factor(), metallic: pbr.metallic_factor(), @@ -682,7 +682,7 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle< Some(Face::Back) }, occlusion_texture, - emissive: Color::rgba(emissive[0], emissive[1], emissive[2], 1.0), + emissive: Color::rgb_linear(emissive[0], emissive[1], emissive[2]), emissive_texture, unlit: material.unlit(), alpha_mode: alpha_mode(material), diff --git a/crates/bevy_hierarchy/src/child_builder.rs b/crates/bevy_hierarchy/src/child_builder.rs index a5c5d6708a667..9e2c328348d04 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,20 +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) { - debug_assert!(parent != previous); - remove_from_children(world, previous, *child); - moved.push(HierarchyEvent::ChildMoved { - child: *child, + 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); + 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) { @@ -104,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); } } @@ -144,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); } } @@ -163,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); } } @@ -194,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(); } } @@ -215,15 +214,6 @@ pub struct ChildBuilder<'w, 's, 'a> { } impl<'w, 's, 'a> ChildBuilder<'w, 's, 'a> { - /// Spawns an entity with the given bundle and inserts it into the children defined by the [`ChildBuilder`] - #[deprecated( - since = "0.9.0", - note = "Use `spawn` instead, which now accepts bundles, components, and tuples of bundles and components." - )] - pub fn spawn_bundle(&mut self, bundle: impl Bundle) -> EntityCommands<'w, 's, '_> { - self.spawn(bundle) - } - /// Spawns an entity with the given bundle and inserts it into the children defined by the [`ChildBuilder`] pub fn spawn(&mut self, bundle: impl Bundle) -> EntityCommands<'w, 's, '_> { let e = self.commands.spawn(bundle); @@ -253,41 +243,9 @@ 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_bundle(MoreStuff); - /// }); - /// # } - /// ``` - fn add_children(&mut self, f: impl FnOnce(&mut ChildBuilder) -> T) -> T; - /// Pushes children to the back of the builder's children + /// 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. /// /// 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 @@ -317,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(), @@ -331,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 { @@ -389,56 +341,41 @@ impl<'w, 's, 'a> BuildChildren for EntityCommands<'w, 's, 'a> { #[derive(Debug)] pub struct WorldChildBuilder<'w> { world: &'w mut World, - current_entity: Option, - parent_entities: Vec, + parent: Entity, } impl<'w> WorldChildBuilder<'w> { /// Spawns an entity with the given bundle and inserts it into the children defined by the [`WorldChildBuilder`] pub fn spawn(&mut self, bundle: impl Bundle + Send + Sync + 'static) -> EntityMut<'_> { - let parent_entity = self.parent_entity(); - let entity = self.world.spawn((bundle, Parent(parent_entity))).id(); - push_child_unchecked(self.world, parent_entity, entity); - self.current_entity = Some(entity); - if let Some(mut added) = self.world.get_resource_mut::>() { - added.send(HierarchyEvent::ChildAdded { + let entity = self.world.spawn((bundle, Parent(self.parent))).id(); + push_child_unchecked(self.world, self.parent, entity); + push_events( + self.world, + [HierarchyEvent::ChildAdded { child: entity, - parent: parent_entity, - }); - } + parent: self.parent, + }], + ); self.world.entity_mut(entity) } - #[deprecated( - since = "0.9.0", - note = "Use `spawn` instead, which now accepts bundles, components, and tuples of bundles and components." - )] - /// Spawns an entity with the given bundle and inserts it into the children defined by the [`WorldChildBuilder`] - pub fn spawn_bundle(&mut self, bundle: impl Bundle + Send + Sync + 'static) -> EntityMut<'_> { - self.spawn(bundle) - } - /// Spawns an [`Entity`] with no components and inserts it into the children defined by the [`WorldChildBuilder`] which adds the [`Parent`] component to it. pub fn spawn_empty(&mut self) -> EntityMut<'_> { - let parent_entity = self.parent_entity(); - let entity = self.world.spawn(Parent(parent_entity)).id(); - push_child_unchecked(self.world, parent_entity, entity); - self.current_entity = Some(entity); - if let Some(mut added) = self.world.get_resource_mut::>() { - added.send(HierarchyEvent::ChildAdded { + let entity = self.world.spawn(Parent(self.parent)).id(); + push_child_unchecked(self.world, self.parent, entity); + push_events( + self.world, + [HierarchyEvent::ChildAdded { child: entity, - parent: parent_entity, - }); - } + parent: self.parent, + }], + ); self.world.entity_mut(entity) } /// Returns the parent entity of this [`WorldChildBuilder`] pub fn parent_entity(&self) -> Entity { - self.parent_entities - .last() - .cloned() - .expect("There should always be a parent at this point.") + self.parent } } @@ -446,28 +383,53 @@ 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> { fn with_children(&mut self, spawn_children: impl FnOnce(&mut WorldChildBuilder)) -> &mut Self { - let entity = self.id(); + let parent = self.id(); self.world_scope(|world| { - let mut builder = WorldChildBuilder { - current_entity: None, - parent_entities: vec![entity], - world, - }; - spawn_children(&mut builder); + spawn_children(&mut WorldChildBuilder { world, parent }); }); 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| { @@ -507,84 +469,172 @@ impl<'w> BuildWorldChildren for EntityMut<'w> { }); self } -} - -impl<'w> BuildWorldChildren for WorldChildBuilder<'w> { - fn with_children( - &mut self, - spawn_children: impl FnOnce(&mut WorldChildBuilder<'w>), - ) -> &mut Self { - let current_entity = self - .current_entity - .expect("Cannot add children without a parent. Try creating an entity first."); - self.parent_entities.push(current_entity); - self.current_entity = None; - - spawn_children(self); - - self.current_entity = self.parent_entities.pop(); - self - } - fn push_children(&mut self, children: &[Entity]) -> &mut Self { - let parent = self - .current_entity - .expect("Cannot add children without a parent. Try creating an entity first."); - update_old_parents(self.world, parent, children); - if let Some(mut children_component) = self.world.get_mut::(parent) { - children_component - .0 - .retain(|value| !children.contains(value)); - children_component.0.extend(children.iter().cloned()); - } else { - self.world - .entity_mut(parent) - .insert(Children::from_entities(children)); - } + 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 insert_children(&mut self, index: usize, children: &[Entity]) -> &mut Self { - let parent = self - .current_entity - .expect("Cannot add children without a parent. Try creating an entity first."); - update_old_parents(self.world, parent, children); - if let Some(mut children_component) = self.world.get_mut::(parent) { - children_component - .0 - .retain(|value| !children.contains(value)); - children_component.0.insert_from_slice(index, children); - } else { - self.world - .entity_mut(parent) - .insert(Children::from_entities(children)); + 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 } - - fn remove_children(&mut self, children: &[Entity]) -> &mut Self { - let parent = self - .current_entity - .expect("Cannot remove children without a parent. Try creating an entity first."); - - remove_children(parent, children, self.world); - 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); @@ -595,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); @@ -815,4 +866,19 @@ mod tests { let child = world.spawn_empty().id(); world.spawn_empty().push_children(&[child]); } + + #[test] + fn push_children_idempotent() { + let mut world = World::new(); + let child = world.spawn_empty().id(); + let parent = world + .spawn_empty() + .push_children(&[child]) + .push_children(&[child]) + .id(); + + let mut query = world.query::<&Children>(); + let children = query.get(&world, parent).unwrap(); + assert_eq!(**children, [child]); + } } diff --git a/crates/bevy_hierarchy/src/components/parent.rs b/crates/bevy_hierarchy/src/components/parent.rs index d22fa67d3fce7..3e573be5a6952 100644 --- a/crates/bevy_hierarchy/src/components/parent.rs +++ b/crates/bevy_hierarchy/src/components/parent.rs @@ -31,7 +31,7 @@ impl Parent { // better ways to handle cases like this. impl FromWorld for Parent { fn from_world(_world: &mut World) -> Self { - Parent(Entity::from_raw(u32::MAX)) + Parent(Entity::PLACEHOLDER) } } diff --git a/crates/bevy_hierarchy/src/events.rs b/crates/bevy_hierarchy/src/events.rs index 64f1d2118da84..f68c2a0788a7d 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_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index 521dea46533c9..c4f1ea22da099 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -1,6 +1,9 @@ use crate::{Axis, Input}; use bevy_ecs::event::{EventReader, EventWriter}; -use bevy_ecs::system::{Res, ResMut, Resource}; +use bevy_ecs::{ + change_detection::DetectChanges, + system::{Res, ResMut, Resource}, +}; use bevy_reflect::{std_traits::ReflectDefault, FromReflect, Reflect}; use bevy_utils::{tracing::info, HashMap}; use thiserror::Error; @@ -1160,7 +1163,7 @@ pub fn gamepad_event_system( mut events: EventWriter, settings: Res, ) { - button_input.clear(); + button_input.bypass_change_detection().clear(); for event in raw_events.iter() { match &event.event_type { GamepadEventType::Connected(_) => { diff --git a/crates/bevy_input/src/input.rs b/crates/bevy_input/src/input.rs index 8e4556f9ae901..3d1a1ecf5a9ad 100644 --- a/crates/bevy_input/src/input.rs +++ b/crates/bevy_input/src/input.rs @@ -34,6 +34,13 @@ use bevy_ecs::schedule::State; /// * Call the [`Input::press`] method for each press event. /// * Call the [`Input::release`] method for each release event. /// * Call the [`Input::clear`] method at each frame start, before processing events. +/// +/// Note: Calling `clear` from a [`ResMut`] will trigger change detection. +/// It may be preferable to use [`DetectChanges::bypass_change_detection`] +/// to avoid causing the resource to always be marked as changed. +/// +///[`ResMut`]: bevy_ecs::system::ResMut +///[`DetectChanges::bypass_change_detection`]: bevy_ecs::change_detection::DetectChanges::bypass_change_detection #[derive(Debug, Clone, Resource, Reflect)] #[reflect(Default)] pub struct Input { diff --git a/crates/bevy_input/src/keyboard.rs b/crates/bevy_input/src/keyboard.rs index e2d174b6a51b5..021646015be7c 100644 --- a/crates/bevy_input/src/keyboard.rs +++ b/crates/bevy_input/src/keyboard.rs @@ -1,5 +1,5 @@ use crate::{ButtonState, Input}; -use bevy_ecs::{event::EventReader, system::ResMut}; +use bevy_ecs::{change_detection::DetectChanges, event::EventReader, system::ResMut}; use bevy_reflect::{FromReflect, Reflect}; #[cfg(feature = "serialize")] @@ -41,8 +41,9 @@ pub fn keyboard_input_system( mut key_input: ResMut>, mut keyboard_input_events: EventReader, ) { - scan_input.clear(); - key_input.clear(); + // Avoid clearing if it's not empty to ensure change detection is not triggered. + scan_input.bypass_change_detection().clear(); + key_input.bypass_change_detection().clear(); for event in keyboard_input_events.iter() { let KeyboardInput { scan_code, state, .. diff --git a/crates/bevy_input/src/mouse.rs b/crates/bevy_input/src/mouse.rs index 50d92e52c0273..bf86ed55b22ea 100644 --- a/crates/bevy_input/src/mouse.rs +++ b/crates/bevy_input/src/mouse.rs @@ -1,5 +1,5 @@ use crate::{ButtonState, Input}; -use bevy_ecs::{event::EventReader, system::ResMut}; +use bevy_ecs::{change_detection::DetectChanges, event::EventReader, system::ResMut}; use bevy_math::Vec2; use bevy_reflect::{FromReflect, Reflect}; @@ -132,7 +132,7 @@ pub fn mouse_button_input_system( mut mouse_button_input: ResMut>, mut mouse_button_input_events: EventReader, ) { - mouse_button_input.clear(); + mouse_button_input.bypass_change_detection().clear(); for event in mouse_button_input_events.iter() { match event.state { ButtonState::Pressed => mouse_button_input.press(event.button), diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index 64614bbd4f9dd..68dbc7ac89fba 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -1,25 +1,25 @@ use bevy_app::{PluginGroup, PluginGroupBuilder}; /// This plugin group will add all the default plugins: -/// * [`LogPlugin`](bevy_log::LogPlugin) -/// * [`CorePlugin`](bevy_core::CorePlugin) -/// * [`TimePlugin`](bevy_time::TimePlugin) -/// * [`TransformPlugin`](bevy_transform::TransformPlugin) -/// * [`HierarchyPlugin`](bevy_hierarchy::HierarchyPlugin) -/// * [`DiagnosticsPlugin`](bevy_diagnostic::DiagnosticsPlugin) -/// * [`InputPlugin`](bevy_input::InputPlugin) -/// * [`WindowPlugin`](bevy_window::WindowPlugin) -/// * [`AssetPlugin`](bevy_asset::AssetPlugin) -/// * [`ScenePlugin`](bevy_scene::ScenePlugin) -/// * [`RenderPlugin`](bevy_render::RenderPlugin) - with feature `bevy_render` -/// * [`SpritePlugin`](bevy_sprite::SpritePlugin) - with feature `bevy_sprite` -/// * [`PbrPlugin`](bevy_pbr::PbrPlugin) - with feature `bevy_pbr` -/// * [`UiPlugin`](bevy_ui::UiPlugin) - with feature `bevy_ui` -/// * [`TextPlugin`](bevy_text::TextPlugin) - with feature `bevy_text` -/// * [`AudioPlugin`](bevy_audio::AudioPlugin) - with feature `bevy_audio` -/// * [`GilrsPlugin`](bevy_gilrs::GilrsPlugin) - with feature `bevy_gilrs` -/// * [`GltfPlugin`](bevy_gltf::GltfPlugin) - with feature `bevy_gltf` -/// * [`WinitPlugin`](bevy_winit::WinitPlugin) - with feature `bevy_winit` +/// * [`LogPlugin`](crate::log::LogPlugin) +/// * [`CorePlugin`](crate::core::CorePlugin) +/// * [`TimePlugin`](crate::time::TimePlugin) +/// * [`TransformPlugin`](crate::transform::TransformPlugin) +/// * [`HierarchyPlugin`](crate::hierarchy::HierarchyPlugin) +/// * [`DiagnosticsPlugin`](crate::diagnostic::DiagnosticsPlugin) +/// * [`InputPlugin`](crate::input::InputPlugin) +/// * [`WindowPlugin`](crate::window::WindowPlugin) +/// * [`AssetPlugin`](crate::asset::AssetPlugin) +/// * [`ScenePlugin`](crate::scene::ScenePlugin) +/// * [`RenderPlugin`](crate::render::RenderPlugin) - with feature `bevy_render` +/// * [`SpritePlugin`](crate::sprite::SpritePlugin) - with feature `bevy_sprite` +/// * [`PbrPlugin`](crate::pbr::PbrPlugin) - with feature `bevy_pbr` +/// * [`UiPlugin`](crate::ui::UiPlugin) - with feature `bevy_ui` +/// * [`TextPlugin`](crate::text::TextPlugin) - with feature `bevy_text` +/// * [`AudioPlugin`](crate::audio::AudioPlugin) - with feature `bevy_audio` +/// * [`GilrsPlugin`](crate::gilrs::GilrsPlugin) - with feature `bevy_gilrs` +/// * [`GltfPlugin`](crate::gltf::GltfPlugin) - with feature `bevy_gltf` +/// * [`WinitPlugin`](crate::winit::WinitPlugin) - with feature `bevy_winit` /// /// See also [`MinimalPlugins`] for a slimmed down option pub struct DefaultPlugins; @@ -118,9 +118,9 @@ impl PluginGroup for DefaultPlugins { } /// Minimal plugin group that will add the following plugins: -/// * [`CorePlugin`](bevy_core::CorePlugin) -/// * [`TimePlugin`](bevy_time::TimePlugin) -/// * [`ScheduleRunnerPlugin`](bevy_app::ScheduleRunnerPlugin) +/// * [`CorePlugin`](crate::core::CorePlugin) +/// * [`TimePlugin`](crate::time::TimePlugin) +/// * [`ScheduleRunnerPlugin`](crate::app::ScheduleRunnerPlugin) /// /// See also [`DefaultPlugins`] for a more complete set of plugins pub struct MinimalPlugins; diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index 90482b3e3615b..a1e42d64a12fe 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/Cargo.toml b/crates/bevy_log/Cargo.toml index a4f45028b1406..5f59ce11454f8 100644 --- a/crates/bevy_log/Cargo.toml +++ b/crates/bevy_log/Cargo.toml @@ -17,7 +17,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.9.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.9.0" } tracing-subscriber = {version = "0.3.1", features = ["registry", "env-filter"]} -tracing-chrome = { version = "0.6.0", optional = true } +tracing-chrome = { version = "0.7.0", optional = true } tracing-tracy = { version = "0.10.0", optional = true } tracing-log = "0.1.2" tracing-error = { version = "0.2.0", optional = true } diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index d8e1d6a2a68bc..7265f4fd62170 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -114,8 +114,8 @@ impl Plugin for LogPlugin { })); } + let finished_subscriber; let default_filter = { format!("{},{}", self.level, self.filter) }; - LogTracer::init().unwrap(); let filter_layer = EnvFilter::try_from_default_env() .or_else(|_| EnvFilter::try_new(&default_filter)) .unwrap(); @@ -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); @@ -166,25 +170,33 @@ impl Plugin for LogPlugin { #[cfg(feature = "tracing-tracy")] let subscriber = subscriber.with(tracy_layer); - bevy_utils::tracing::subscriber::set_global_default(subscriber) - .expect("Could not set global default tracing subscriber. If you've already set up a tracing subscriber, please disable LogPlugin from Bevy's DefaultPlugins"); + finished_subscriber = subscriber; } #[cfg(target_arch = "wasm32")] { console_error_panic_hook::set_once(); - let subscriber = subscriber.with(tracing_wasm::WASMLayer::new( + finished_subscriber = subscriber.with(tracing_wasm::WASMLayer::new( tracing_wasm::WASMLayerConfig::default(), )); - bevy_utils::tracing::subscriber::set_global_default(subscriber) - .expect("Could not set global default tracing subscriber. If you've already set up a tracing subscriber, please disable LogPlugin from Bevy's DefaultPlugins"); } #[cfg(target_os = "android")] { - let subscriber = subscriber.with(android_tracing::AndroidLayer::default()); - bevy_utils::tracing::subscriber::set_global_default(subscriber) - .expect("Could not set global default tracing subscriber. If you've already set up a tracing subscriber, please disable LogPlugin from Bevy's DefaultPlugins"); + finished_subscriber = subscriber.with(android_tracing::AndroidLayer::default()); + } + + let logger_already_set = LogTracer::init().is_err(); + let subscriber_already_set = + bevy_utils::tracing::subscriber::set_global_default(finished_subscriber).is_err(); + + match (logger_already_set, subscriber_already_set) { + (true, true) => warn!( + "Could not set global logger and tracing subscriber as they are already set. Consider disabling LogPlugin." + ), + (true, _) => warn!("Could not set global logger as it is already set. Consider disabling LogPlugin."), + (_, true) => warn!("Could not set global tracing subscriber as it is already set. Consider disabling LogPlugin."), + _ => (), } } } diff --git a/crates/bevy_math/src/ray.rs b/crates/bevy_math/src/ray.rs index bf0a8f47310d8..b4d8e7b03f5a8 100644 --- a/crates/bevy_math/src/ray.rs +++ b/crates/bevy_math/src/ray.rs @@ -6,6 +6,59 @@ use crate::Vec3; pub struct Ray { /// The origin of the ray. pub origin: Vec3, - /// The direction of the ray. + /// A normalized vector representing the direction of the ray. pub direction: Vec3, } + +impl Ray { + /// Returns the distance to the plane if the ray intersects it. + #[inline] + pub fn intersect_plane(&self, plane_origin: Vec3, plane_normal: Vec3) -> Option { + let denominator = plane_normal.dot(self.direction); + if denominator.abs() > f32::EPSILON { + let distance = (plane_origin - self.origin).dot(plane_normal) / denominator; + if distance > f32::EPSILON { + return Some(distance); + } + } + None + } + + /// Retrieve a point at the given distance along the ray. + #[inline] + pub fn get_point(&self, distance: f32) -> Vec3 { + self.origin + self.direction * distance + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn intersect_plane() { + let ray = Ray { + origin: Vec3::ZERO, + direction: Vec3::Z, + }; + + // Orthogonal, and test that an inverse plane_normal has the same result + assert_eq!(Some(1.), ray.intersect_plane(Vec3::Z, Vec3::Z)); + assert_eq!(Some(1.), ray.intersect_plane(Vec3::Z, Vec3::NEG_Z)); + assert_eq!(None, ray.intersect_plane(Vec3::NEG_Z, Vec3::Z)); + assert_eq!(None, ray.intersect_plane(Vec3::NEG_Z, Vec3::NEG_Z)); + + // Diagonal + assert_eq!(Some(1.), ray.intersect_plane(Vec3::Z, Vec3::ONE)); + assert_eq!(None, ray.intersect_plane(Vec3::NEG_Z, Vec3::ONE)); + + // Parallel + assert_eq!(None, ray.intersect_plane(Vec3::X, Vec3::X)); + + // Parallel with simulated rounding error + assert_eq!( + None, + ray.intersect_plane(Vec3::X, Vec3::X + Vec3::Z * f32::EPSILON) + ); + } +} diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index de10b2f7f5b9f..cd7c04cbfbd69 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -256,19 +256,15 @@ impl Plugin for PbrPlugin { .get_sub_graph_mut(bevy_core_pipeline::core_3d::graph::NAME) .unwrap(); draw_3d_graph.add_node(draw_3d_graph::node::SHADOW_PASS, shadow_pass_node); - draw_3d_graph - .add_node_edge( - draw_3d_graph::node::SHADOW_PASS, - bevy_core_pipeline::core_3d::graph::node::MAIN_PASS, - ) - .unwrap(); - draw_3d_graph - .add_slot_edge( - draw_3d_graph.input_node().unwrap().id, - bevy_core_pipeline::core_3d::graph::input::VIEW_ENTITY, - draw_3d_graph::node::SHADOW_PASS, - ShadowPassNode::IN_VIEW, - ) - .unwrap(); + draw_3d_graph.add_node_edge( + draw_3d_graph::node::SHADOW_PASS, + bevy_core_pipeline::core_3d::graph::node::MAIN_PASS, + ); + draw_3d_graph.add_slot_edge( + draw_3d_graph.input_node().id, + bevy_core_pipeline::core_3d::graph::input::VIEW_ENTITY, + draw_3d_graph::node::SHADOW_PASS, + ShadowPassNode::IN_VIEW, + ); } } diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 2dc0e0f3ed59e..c1d0fc8297325 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -100,12 +100,8 @@ use std::marker::PhantomData; /// In WGSL shaders, the material's binding would look like this: /// /// ```wgsl -/// struct CustomMaterial { -/// color: vec4, -/// } -/// /// @group(1) @binding(0) -/// var material: CustomMaterial; +/// var color: vec4; /// @group(1) @binding(1) /// var color_texture: texture_2d; /// @group(1) @binding(2) @@ -347,18 +343,9 @@ pub fn queue_material_meshes( mut transparent_phase, ) in &mut views { - let draw_opaque_pbr = opaque_draw_functions - .read() - .get_id::>() - .unwrap(); - let draw_alpha_mask_pbr = alpha_mask_draw_functions - .read() - .get_id::>() - .unwrap(); - let draw_transparent_pbr = transparent_draw_functions - .read() - .get_id::>() - .unwrap(); + let draw_opaque_pbr = opaque_draw_functions.read().id::>(); + let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::>(); + let draw_transparent_pbr = transparent_draw_functions.read().id::>(); let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples) | MeshPipelineKey::from_hdr(view.hdr); diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index 8e48acf5821c6..da179b7ccd095 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -419,7 +419,7 @@ impl Material for StandardMaterial { .as_mut() .unwrap() .shader_defs - .push(String::from("STANDARDMATERIAL_NORMAL_MAP")); + .push("STANDARDMATERIAL_NORMAL_MAP".into()); } descriptor.primitive.cull_mode = key.bind_group_data.cull_mode; if let Some(label) = &mut descriptor.label { diff --git a/crates/bevy_pbr/src/render/clustered_forward.wgsl b/crates/bevy_pbr/src/render/clustered_forward.wgsl index 674f996dd578f..c291aee2ff33d 100644 --- a/crates/bevy_pbr/src/render/clustered_forward.wgsl +++ b/crates/bevy_pbr/src/render/clustered_forward.wgsl @@ -29,7 +29,9 @@ fn fragment_cluster_index(frag_coord: vec2, view_z: f32, is_orthographic: b // this must match CLUSTER_COUNT_SIZE in light.rs let CLUSTER_COUNT_SIZE = 9u; fn unpack_offset_and_counts(cluster_index: u32) -> vec3 { -#ifdef NO_STORAGE_BUFFERS_SUPPORT +#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 + return cluster_offsets_and_counts.data[cluster_index].xyz; +#else let offset_and_counts = cluster_offsets_and_counts.data[cluster_index >> 2u][cluster_index & ((1u << 2u) - 1u)]; // [ 31 .. 18 | 17 .. 9 | 8 .. 0 ] // [ offset | point light count | spot light count ] @@ -38,20 +40,18 @@ fn unpack_offset_and_counts(cluster_index: u32) -> vec3 { (offset_and_counts >> CLUSTER_COUNT_SIZE) & ((1u << CLUSTER_COUNT_SIZE) - 1u), offset_and_counts & ((1u << CLUSTER_COUNT_SIZE) - 1u), ); -#else - return cluster_offsets_and_counts.data[cluster_index].xyz; #endif } fn get_light_id(index: u32) -> u32 { -#ifdef NO_STORAGE_BUFFERS_SUPPORT +#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 + return cluster_light_index_lists.data[index]; +#else // The index is correct but in cluster_light_index_lists we pack 4 u8s into a u32 // This means the index into cluster_light_index_lists is index / 4 let indices = cluster_light_index_lists.data[index >> 4u][(index >> 2u) & ((1u << 2u) - 1u)]; // And index % 4 gives the sub-index of the u8 within the u32 so we shift by 8 * sub-index return (indices >> (8u * (index & ((1u << 2u) - 1u)))) & ((1u << 8u) - 1u); -#else - return cluster_light_index_lists.data[index]; #endif } diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index b11aecff4a6bc..9a821073b0d0d 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -321,11 +321,15 @@ impl SpecializedMeshPipeline for ShadowPipeline { let mut bind_group_layout = vec![self.view_layout.clone()]; let mut shader_defs = Vec::new(); + shader_defs.push(ShaderDefVal::UInt( + "MAX_DIRECTIONAL_LIGHTS".to_string(), + MAX_DIRECTIONAL_LIGHTS as u32, + )); if layout.contains(Mesh::ATTRIBUTE_JOINT_INDEX) && layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT) { - shader_defs.push(String::from("SKINNED")); + shader_defs.push("SKINNED".into()); vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_INDEX.at_shader_location(4)); vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(5)); bind_group_layout.push(self.skinned_mesh_layout.clone()); @@ -1631,10 +1635,7 @@ pub fn queue_shadows( spot_light_entities: Query<&VisibleEntities, With>, ) { for view_lights in &view_lights { - let draw_shadow_mesh = shadow_draw_functions - .read() - .get_id::() - .unwrap(); + let draw_shadow_mesh = shadow_draw_functions.read().id::(); for view_light_entity in view_lights.lights.iter().copied() { let (light_entity, mut shadow_phase) = view_light_shadow_phases.get_mut(view_light_entity).unwrap(); @@ -1784,16 +1785,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); } } diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 73e7eea7b0c5f..bf3b181913a70 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1,7 +1,7 @@ use crate::{ GlobalLightMeta, GpuLights, GpuPointLights, LightMeta, NotShadowCaster, NotShadowReceiver, ShadowPipeline, ViewClusterBindings, ViewLightsUniformOffset, ViewShadowBindings, - CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, + CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_DIRECTIONAL_LIGHTS, }; use bevy_app::Plugin; use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped}; @@ -172,11 +172,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 +183,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 +219,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 +234,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 +242,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); } @@ -581,27 +577,32 @@ impl SpecializedMeshPipeline for MeshPipeline { let mut vertex_attributes = Vec::new(); if layout.contains(Mesh::ATTRIBUTE_POSITION) { - shader_defs.push(String::from("VERTEX_POSITIONS")); + shader_defs.push("VERTEX_POSITIONS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0)); } if layout.contains(Mesh::ATTRIBUTE_NORMAL) { - shader_defs.push(String::from("VERTEX_NORMALS")); + shader_defs.push("VERTEX_NORMALS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(1)); } + shader_defs.push(ShaderDefVal::UInt( + "MAX_DIRECTIONAL_LIGHTS".to_string(), + MAX_DIRECTIONAL_LIGHTS as u32, + )); + if layout.contains(Mesh::ATTRIBUTE_UV_0) { - shader_defs.push(String::from("VERTEX_UVS")); + shader_defs.push("VERTEX_UVS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(2)); } if layout.contains(Mesh::ATTRIBUTE_TANGENT) { - shader_defs.push(String::from("VERTEX_TANGENTS")); + shader_defs.push("VERTEX_TANGENTS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3)); } if layout.contains(Mesh::ATTRIBUTE_COLOR) { - shader_defs.push(String::from("VERTEX_COLORS")); + shader_defs.push("VERTEX_COLORS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(4)); } @@ -609,7 +610,7 @@ impl SpecializedMeshPipeline for MeshPipeline { if layout.contains(Mesh::ATTRIBUTE_JOINT_INDEX) && layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT) { - shader_defs.push(String::from("SKINNED")); + shader_defs.push("SKINNED".into()); vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_INDEX.at_shader_location(5)); vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(6)); bind_group_layout.push(self.skinned_mesh_layout.clone()); @@ -636,11 +637,11 @@ impl SpecializedMeshPipeline for MeshPipeline { } if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) { - shader_defs.push("TONEMAP_IN_SHADER".to_string()); + shader_defs.push("TONEMAP_IN_SHADER".into()); // Debanding is tied to tonemapping in the shader, cannot run without it. if key.contains(MeshPipelineKey::DEBAND_DITHER) { - shader_defs.push("DEBAND_DITHER".to_string()); + shader_defs.push("DEBAND_DITHER".into()); } } @@ -774,20 +775,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); diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl index 9756ff05d6501..cfe9ae87ef6a3 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -25,20 +25,20 @@ var directional_shadow_textures: texture_depth_2d_array; @group(0) @binding(5) var directional_shadow_textures_sampler: sampler_comparison; -#ifdef NO_STORAGE_BUFFERS_SUPPORT +#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 @group(0) @binding(6) -var point_lights: PointLights; +var point_lights: PointLights; @group(0) @binding(7) -var cluster_light_index_lists: ClusterLightIndexLists; +var cluster_light_index_lists: ClusterLightIndexLists; @group(0) @binding(8) -var cluster_offsets_and_counts: ClusterOffsetsAndCounts; +var cluster_offsets_and_counts: ClusterOffsetsAndCounts; #else @group(0) @binding(6) -var point_lights: PointLights; +var point_lights: PointLights; @group(0) @binding(7) -var cluster_light_index_lists: ClusterLightIndexLists; +var cluster_light_index_lists: ClusterLightIndexLists; @group(0) @binding(8) -var cluster_offsets_and_counts: ClusterOffsetsAndCounts; +var cluster_offsets_and_counts: ClusterOffsetsAndCounts; #endif @group(0) @binding(9) diff --git a/crates/bevy_pbr/src/render/mesh_view_types.wgsl b/crates/bevy_pbr/src/render/mesh_view_types.wgsl index ab595b9e01f9c..c6316f59652dc 100644 --- a/crates/bevy_pbr/src/render/mesh_view_types.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_types.wgsl @@ -42,7 +42,7 @@ let DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u; struct Lights { // NOTE: this array size must be kept in sync with the constants defined in bevy_pbr/src/render/light.rs - directional_lights: array, + directional_lights: array, ambient_color: vec4, // x/y/z dimensions and n_clusters in w cluster_dimensions: vec4, @@ -61,28 +61,28 @@ struct Lights { spot_light_shadowmap_offset: i32, }; -#ifdef NO_STORAGE_BUFFERS_SUPPORT +#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 struct PointLights { - data: array, + data: array, }; struct ClusterLightIndexLists { - // each u32 contains 4 u8 indices into the PointLights array - data: array, 1024u>, + data: array, }; struct ClusterOffsetsAndCounts { - // each u32 contains a 24-bit index into ClusterLightIndexLists in the high 24 bits - // and an 8-bit count of the number of lights in the low 8 bits - data: array, 1024u>, + data: array>, }; #else struct PointLights { - data: array, + data: array, }; struct ClusterLightIndexLists { - data: array, + // each u32 contains 4 u8 indices into the PointLights array + data: array, 1024u>, }; struct ClusterOffsetsAndCounts { - data: array>, + // each u32 contains a 24-bit index into ClusterLightIndexLists in the high 24 bits + // and an 8-bit count of the number of lights in the low 8 bits + data: array, 1024u>, }; #endif diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index 6f5d94edfbaf3..cfe825190d2a5 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -99,7 +99,13 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { output_color = tone_mapping(output_color); #endif #ifdef DEBAND_DITHER - output_color = dither(output_color, in.frag_coord.xy); + var output_rgb = output_color.rgb; + output_rgb = pow(output_rgb, vec3(1.0 / 2.2)); + output_rgb = output_rgb + screen_space_dither(in.frag_coord.xy); + // This conversion back to linear space is required because our output texture format is + // SRGB; the GPU will assume our output is linear and will apply an SRGB conversion. + output_rgb = pow(output_rgb, vec3(2.2)); + output_color = vec4(output_rgb, output_color.a); #endif return output_color; } diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 261dfe9564711..bb552b560f134 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -196,35 +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 { + if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_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 { + if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_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 { + if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_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 97ad2dd687b1c..88a5ea385f57d 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 3953edc041a10..70c2ed3383a2e 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_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 47171ba392834..2b31a051317d2 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -116,10 +116,7 @@ fn queue_wireframes( )>, mut views: Query<(&ExtractedView, &VisibleEntities, &mut RenderPhase)>, ) { - let draw_custom = opaque_3d_draw_functions - .read() - .get_id::() - .unwrap(); + let draw_custom = opaque_3d_draw_functions.read().id::(); let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples); for (view, visible_entities, mut opaque_phase) in &mut views { let rangefinder = view.rangefinder3d(); diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 4aec221350cb6..bf38fd5ff1d9f 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/bevy_reflect_derive/src/container_attributes.rs b/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs index 1575a8adde005..94749f0e8a4e7 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs @@ -5,6 +5,7 @@ //! the derive helper attribute for `Reflect`, which looks like: //! `#[reflect(PartialEq, Default, ...)]` and `#[reflect_value(PartialEq, Default, ...)]`. +use crate::fq_std::{FQAny, FQDefault, FQOption}; use crate::utility; use proc_macro2::{Ident, Span}; use quote::quote_spanned; @@ -222,17 +223,17 @@ impl ReflectTraits { pub fn get_hash_impl(&self, bevy_reflect_path: &Path) -> Option { match &self.hash { &TraitImpl::Implemented(span) => Some(quote_spanned! {span=> - fn reflect_hash(&self) -> Option { - use std::hash::{Hash, Hasher}; - let mut hasher = #bevy_reflect_path::ReflectHasher::default(); - Hash::hash(&std::any::Any::type_id(self), &mut hasher); + fn reflect_hash(&self) -> #FQOption { + use ::core::hash::{Hash, Hasher}; + let mut hasher: #bevy_reflect_path::ReflectHasher = #FQDefault::default(); + Hash::hash(&#FQAny::type_id(self), &mut hasher); Hash::hash(self, &mut hasher); - Some(hasher.finish()) + #FQOption::Some(Hasher::finish(&hasher)) } }), &TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=> - fn reflect_hash(&self) -> Option { - Some(#impl_fn(self)) + fn reflect_hash(&self) -> #FQOption { + #FQOption::Some(#impl_fn(self)) } }), TraitImpl::NotImplemented => None, @@ -248,18 +249,18 @@ impl ReflectTraits { ) -> Option { match &self.partial_eq { &TraitImpl::Implemented(span) => Some(quote_spanned! {span=> - fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> Option { - let value = value.as_any(); - if let Some(value) = value.downcast_ref::() { - Some(std::cmp::PartialEq::eq(self, value)) + fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> #FQOption { + let value = ::as_any(value); + if let #FQOption::Some(value) = ::downcast_ref::(value) { + #FQOption::Some(::core::cmp::PartialEq::eq(self, value)) } else { - Some(false) + #FQOption::Some(false) } } }), &TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=> - fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> Option { - Some(#impl_fn(self, value)) + fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> #FQOption { + #FQOption::Some(#impl_fn(self, value)) } }), TraitImpl::NotImplemented => None, @@ -272,12 +273,12 @@ impl ReflectTraits { pub fn get_debug_impl(&self) -> Option { match &self.debug { &TraitImpl::Implemented(span) => Some(quote_spanned! {span=> - fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Debug::fmt(self, f) + fn debug(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + ::core::fmt::Debug::fmt(self, f) } }), &TraitImpl::Custom(ref impl_fn, span) => Some(quote_spanned! {span=> - fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn debug(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { #impl_fn(self, f) } }), diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/documentation.rs b/crates/bevy_reflect/bevy_reflect_derive/src/documentation.rs index 46f58cb0f3237..4bf2474873655 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/documentation.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/documentation.rs @@ -1,5 +1,6 @@ //! Contains code related to documentation reflection (requires the `documentation` feature). +use crate::fq_std::FQOption; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::{Attribute, Lit, Meta}; @@ -69,9 +70,9 @@ impl Documentation { impl ToTokens for Documentation { fn to_tokens(&self, tokens: &mut TokenStream) { if let Some(doc) = self.doc_string() { - quote!(Some(#doc)).to_tokens(tokens); + quote!(#FQOption::Some(#doc)).to_tokens(tokens); } else { - quote!(None).to_tokens(tokens); + quote!(#FQOption::None).to_tokens(tokens); } } } diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/enum_utility.rs b/crates/bevy_reflect/bevy_reflect_derive/src/enum_utility.rs index 8b1140bb6ac71..b8b94c693358d 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/enum_utility.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/enum_utility.rs @@ -1,3 +1,4 @@ +use crate::fq_std::FQDefault; use crate::{ derive_data::{EnumVariantFields, ReflectEnum}, utility::ident_or_index, @@ -39,7 +40,7 @@ pub(crate) fn get_variant_constructors( let constructor_fields = fields.iter().enumerate().map(|(declar_index, field)| { let field_ident = ident_or_index(field.data.ident.as_ref(), declar_index); let field_value = if field.attrs.ignore.is_ignored() { - quote! { Default::default() } + quote! { #FQDefault::default() } } else { let error_repr = field.data.ident.as_ref().map_or_else( || format!("at index {reflect_index}"), diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/fq_std.rs b/crates/bevy_reflect/bevy_reflect_derive/src/fq_std.rs new file mode 100644 index 0000000000000..7a4e8e78bdc16 --- /dev/null +++ b/crates/bevy_reflect/bevy_reflect_derive/src/fq_std.rs @@ -0,0 +1,77 @@ +//! This module contains unit structs that should be used inside `quote!` and `spanned_quote!` using the variable interpolation syntax in place of their equivalent structs and traits present in `std`. +// +//! To create hygienic proc macros, all the names must be its fully qualified form. These unit structs help us to not specify the fully qualified name every single time. +//! +//! # Example +//! Instead of writing this: +//! ```ignore +//! quote!( +//! fn get_id() -> Option { +//! Some(0) +//! } +//! ) +//! ``` +//! Or this: +//! ```ignore +//! quote!( +//! fn get_id() -> ::core::option::Option { +//! ::core::option::Option::Some(0) +//! } +//! ) +//! ``` +//! We should write this: +//! ```ignore +//! use crate::fq_std::FQOption; +//! +//! quote!( +//! fn get_id() -> #FQOption { +//! #FQOption::Some(0) +//! } +//! ) +//! ``` + +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; + +pub(crate) struct FQAny; +pub(crate) struct FQBox; +pub(crate) struct FQClone; +pub(crate) struct FQDefault; +pub(crate) struct FQOption; +pub(crate) struct FQResult; + +impl ToTokens for FQAny { + fn to_tokens(&self, tokens: &mut TokenStream) { + quote!(::core::any::Any).to_tokens(tokens); + } +} + +impl ToTokens for FQBox { + fn to_tokens(&self, tokens: &mut TokenStream) { + quote!(::std::boxed::Box).to_tokens(tokens); + } +} + +impl ToTokens for FQClone { + fn to_tokens(&self, tokens: &mut TokenStream) { + quote!(::core::clone::Clone).to_tokens(tokens); + } +} + +impl ToTokens for FQDefault { + fn to_tokens(&self, tokens: &mut TokenStream) { + quote!(::core::default::Default).to_tokens(tokens); + } +} + +impl ToTokens for FQOption { + fn to_tokens(&self, tokens: &mut TokenStream) { + quote!(::core::option::Option).to_tokens(tokens); + } +} + +impl ToTokens for FQResult { + fn to_tokens(&self, tokens: &mut TokenStream) { + quote!(::core::result::Result).to_tokens(tokens); + } +} diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs b/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs index 73e4cb6451108..aab2545b54989 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/from_reflect.rs @@ -2,10 +2,11 @@ use crate::container_attributes::REFLECT_DEFAULT; use crate::derive_data::ReflectEnum; use crate::enum_utility::{get_variant_constructors, EnumVariantConstructors}; use crate::field_attributes::DefaultBehavior; +use crate::fq_std::{FQAny, FQClone, FQDefault, FQOption}; use crate::{ReflectMeta, ReflectStruct}; use proc_macro::TokenStream; use proc_macro2::Span; -use quote::quote; +use quote::{quote, ToTokens}; use syn::{Field, Ident, Index, Lit, LitInt, LitStr, Member}; /// Implements `FromReflect` for the given struct @@ -25,8 +26,8 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> TokenStream { let (impl_generics, ty_generics, where_clause) = meta.generics().split_for_impl(); TokenStream::from(quote! { impl #impl_generics #bevy_reflect_path::FromReflect for #type_name #ty_generics #where_clause { - fn from_reflect(reflect: &dyn #bevy_reflect_path::Reflect) -> Option { - Some(reflect.as_any().downcast_ref::<#type_name #ty_generics>()?.clone()) + fn from_reflect(reflect: &dyn #bevy_reflect_path::Reflect) -> #FQOption { + #FQOption::Some(#FQClone::clone(::downcast_ref::<#type_name #ty_generics>(::as_any(reflect))?)) } } }) @@ -34,6 +35,8 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> TokenStream { /// Implements `FromReflect` for the given enum type pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream { + let fqoption = FQOption.into_token_stream(); + let type_name = reflect_enum.meta().type_name(); let bevy_reflect_path = reflect_enum.meta().bevy_reflect_path(); @@ -47,14 +50,14 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream { reflect_enum.meta().generics().split_for_impl(); TokenStream::from(quote! { impl #impl_generics #bevy_reflect_path::FromReflect for #type_name #ty_generics #where_clause { - fn from_reflect(#ref_value: &dyn #bevy_reflect_path::Reflect) -> Option { - if let #bevy_reflect_path::ReflectRef::Enum(#ref_value) = #ref_value.reflect_ref() { - match #ref_value.variant_name() { - #(#variant_names => Some(#variant_constructors),)* - name => panic!("variant with name `{}` does not exist on enum `{}`", name, std::any::type_name::()), + fn from_reflect(#ref_value: &dyn #bevy_reflect_path::Reflect) -> #FQOption { + if let #bevy_reflect_path::ReflectRef::Enum(#ref_value) = #bevy_reflect_path::Reflect::reflect_ref(#ref_value) { + match #bevy_reflect_path::Enum::variant_name(#ref_value) { + #(#variant_names => #fqoption::Some(#variant_constructors),)* + name => panic!("variant with name `{}` does not exist on enum `{}`", name, ::core::any::type_name::()), } } else { - None + #FQOption::None } } } @@ -72,6 +75,8 @@ impl MemberValuePair { } fn impl_struct_internal(reflect_struct: &ReflectStruct, is_tuple: bool) -> TokenStream { + let fqoption = FQOption.into_token_stream(); + let struct_name = reflect_struct.meta().type_name(); let generics = reflect_struct.meta().generics(); let bevy_reflect_path = reflect_struct.meta().bevy_reflect_path(); @@ -89,21 +94,21 @@ fn impl_struct_internal(reflect_struct: &ReflectStruct, is_tuple: bool) -> Token let constructor = if reflect_struct.meta().traits().contains(REFLECT_DEFAULT) { quote!( - let mut __this = Self::default(); + let mut __this: Self = #FQDefault::default(); #( - if let Some(__field) = #active_values() { + if let #fqoption::Some(__field) = #active_values() { // Iff field exists -> use its value __this.#active_members = __field; } )* - Some(__this) + #FQOption::Some(__this) ) } else { let MemberValuePair(ignored_members, ignored_values) = get_ignored_fields(reflect_struct, is_tuple); quote!( - Some( + #FQOption::Some( Self { #(#active_members: #active_values()?,)* #(#ignored_members: #ignored_values,)* @@ -129,11 +134,11 @@ fn impl_struct_internal(reflect_struct: &ReflectStruct, is_tuple: bool) -> Token TokenStream::from(quote! { impl #impl_generics #bevy_reflect_path::FromReflect for #struct_name #ty_generics #where_from_reflect_clause { - fn from_reflect(reflect: &dyn #bevy_reflect_path::Reflect) -> Option { - if let #bevy_reflect_path::ReflectRef::#ref_struct_type(#ref_struct) = reflect.reflect_ref() { + fn from_reflect(reflect: &dyn #bevy_reflect_path::Reflect) -> #FQOption { + if let #bevy_reflect_path::ReflectRef::#ref_struct_type(#ref_struct) = #bevy_reflect_path::Reflect::reflect_ref(reflect) { #constructor } else { - None + #FQOption::None } } } @@ -153,7 +158,7 @@ fn get_ignored_fields(reflect_struct: &ReflectStruct, is_tuple: bool) -> MemberV let value = match &field.attrs.default { DefaultBehavior::Func(path) => quote! {#path()}, - _ => quote! {Default::default()}, + _ => quote! {#FQDefault::default()}, }; (member, value) @@ -189,19 +194,19 @@ fn get_active_fields( let value = match &field.attrs.default { DefaultBehavior::Func(path) => quote! { (|| - if let Some(field) = #get_field { + if let #FQOption::Some(field) = #get_field { <#ty as #bevy_reflect_path::FromReflect>::from_reflect(field) } else { - Some(#path()) + #FQOption::Some(#path()) } ) }, DefaultBehavior::Default => quote! { (|| - if let Some(field) = #get_field { + if let #FQOption::Some(field) = #get_field { <#ty as #bevy_reflect_path::FromReflect>::from_reflect(field) } else { - Some(Default::default()) + #FQOption::Some(#FQDefault::default()) } ) }, diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs index a1d10aa7c70a8..ce163092fac5a 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs @@ -1,5 +1,6 @@ use crate::derive_data::{EnumVariant, EnumVariantFields, ReflectEnum, StructField}; use crate::enum_utility::{get_variant_constructors, EnumVariantConstructors}; +use crate::fq_std::{FQAny, FQBox, FQOption, FQResult}; use crate::impls::impl_typed; use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; @@ -37,7 +38,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream { .get_hash_impl(bevy_reflect_path) .unwrap_or_else(|| { quote! { - fn reflect_hash(&self) -> Option { + fn reflect_hash(&self) -> #FQOption { #bevy_reflect_path::enum_hash(self) } } @@ -49,7 +50,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream { .get_partial_eq_impl(bevy_reflect_path) .unwrap_or_else(|| { quote! { - fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> Option { + fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> #FQOption { #bevy_reflect_path::enum_partial_eq(self, value) } } @@ -93,45 +94,45 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream { #typed_impl impl #impl_generics #bevy_reflect_path::Enum for #enum_name #ty_generics #where_clause { - fn field(&self, #ref_name: &str) -> Option<&dyn #bevy_reflect_path::Reflect> { + fn field(&self, #ref_name: &str) -> #FQOption<&dyn #bevy_reflect_path::Reflect> { match self { #(#enum_field,)* - _ => None, + _ => #FQOption::None, } } - fn field_at(&self, #ref_index: usize) -> Option<&dyn #bevy_reflect_path::Reflect> { + fn field_at(&self, #ref_index: usize) -> #FQOption<&dyn #bevy_reflect_path::Reflect> { match self { #(#enum_field_at,)* - _ => None, + _ => #FQOption::None, } } - fn field_mut(&mut self, #ref_name: &str) -> Option<&mut dyn #bevy_reflect_path::Reflect> { + fn field_mut(&mut self, #ref_name: &str) -> #FQOption<&mut dyn #bevy_reflect_path::Reflect> { match self { #(#enum_field,)* - _ => None, + _ => #FQOption::None, } } - fn field_at_mut(&mut self, #ref_index: usize) -> Option<&mut dyn #bevy_reflect_path::Reflect> { + fn field_at_mut(&mut self, #ref_index: usize) -> #FQOption<&mut dyn #bevy_reflect_path::Reflect> { match self { #(#enum_field_at,)* - _ => None, + _ => #FQOption::None, } } - fn index_of(&self, #ref_name: &str) -> Option { + fn index_of(&self, #ref_name: &str) -> #FQOption { match self { #(#enum_index_of,)* - _ => None, + _ => #FQOption::None, } } - fn name_at(&self, #ref_index: usize) -> Option<&str> { + fn name_at(&self, #ref_index: usize) -> #FQOption<&str> { match self { #(#enum_name_at,)* - _ => None, + _ => #FQOption::None, } } @@ -179,7 +180,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream { impl #impl_generics #bevy_reflect_path::Reflect for #enum_name #ty_generics #where_clause { #[inline] fn type_name(&self) -> &str { - std::any::type_name::() + ::core::any::type_name::() } #[inline] @@ -188,22 +189,22 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream { } #[inline] - fn into_any(self: Box) -> Box { + fn into_any(self: #FQBox) -> #FQBox { self } #[inline] - fn as_any(&self) -> &dyn std::any::Any { + fn as_any(&self) -> &dyn #FQAny { self } #[inline] - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + fn as_any_mut(&mut self) -> &mut dyn #FQAny { self } #[inline] - fn into_reflect(self: Box) -> Box { + fn into_reflect(self: #FQBox) -> #FQBox { self } @@ -218,30 +219,30 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream { } #[inline] - fn clone_value(&self) -> Box { - Box::new(#bevy_reflect_path::Enum::clone_dynamic(self)) + fn clone_value(&self) -> #FQBox { + #FQBox::new(#bevy_reflect_path::Enum::clone_dynamic(self)) } #[inline] - fn set(&mut self, #ref_value: Box) -> Result<(), Box> { - *self = #ref_value.take()?; - Ok(()) + fn set(&mut self, #ref_value: #FQBox) -> #FQResult<(), #FQBox> { + *self = ::take(#ref_value)?; + #FQResult::Ok(()) } #[inline] fn apply(&mut self, #ref_value: &dyn #bevy_reflect_path::Reflect) { - if let #bevy_reflect_path::ReflectRef::Enum(#ref_value) = #ref_value.reflect_ref() { - if #bevy_reflect_path::Enum::variant_name(self) == #ref_value.variant_name() { + if let #bevy_reflect_path::ReflectRef::Enum(#ref_value) = #bevy_reflect_path::Reflect::reflect_ref(#ref_value) { + if #bevy_reflect_path::Enum::variant_name(self) == #bevy_reflect_path::Enum::variant_name(#ref_value) { // Same variant -> just update fields - match #ref_value.variant_type() { + match #bevy_reflect_path::Enum::variant_type(#ref_value) { #bevy_reflect_path::VariantType::Struct => { - for field in #ref_value.iter_fields() { + for field in #bevy_reflect_path::Enum::iter_fields(#ref_value) { let name = field.name().unwrap(); #bevy_reflect_path::Enum::field_mut(self, name).map(|v| v.apply(field.value())); } } #bevy_reflect_path::VariantType::Tuple => { - for (index, field) in #ref_value.iter_fields().enumerate() { + for (index, field) in ::core::iter::Iterator::enumerate(#bevy_reflect_path::Enum::iter_fields(#ref_value)) { #bevy_reflect_path::Enum::field_at_mut(self, index).map(|v| v.apply(field.value())); } } @@ -249,15 +250,15 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream { } } else { // New variant -> perform a switch - match #ref_value.variant_name() { + match #bevy_reflect_path::Enum::variant_name(#ref_value) { #(#variant_names => { *self = #variant_constructors })* - name => panic!("variant with name `{}` does not exist on enum `{}`", name, std::any::type_name::()), + name => panic!("variant with name `{}` does not exist on enum `{}`", name, ::core::any::type_name::()), } } } else { - panic!("`{}` is not an enum", #ref_value.type_name()); + panic!("`{}` is not an enum", #bevy_reflect_path::Reflect::type_name(#ref_value)); } } @@ -269,7 +270,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream { #bevy_reflect_path::ReflectMut::Enum(self) } - fn reflect_owned(self: Box) -> #bevy_reflect_path::ReflectOwned { + fn reflect_owned(self: #FQBox) -> #bevy_reflect_path::ReflectOwned { #bevy_reflect_path::ReflectOwned::Enum(self) } @@ -380,7 +381,7 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden let args = get_field_args(fields, |reflect_idx, declaration_index, field| { let declar_field = syn::Index::from(declaration_index); enum_field_at.push(quote! { - #unit { #declar_field : value, .. } if #ref_index == #reflect_idx => Some(value) + #unit { #declar_field : value, .. } if #ref_index == #reflect_idx => #FQOption::Some(value) }); #[cfg(feature = "documentation")] @@ -406,16 +407,16 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden let field_ident = field.data.ident.as_ref().unwrap(); let field_name = field_ident.to_string(); enum_field.push(quote! { - #unit{ #field_ident, .. } if #ref_name == #field_name => Some(#field_ident) + #unit{ #field_ident, .. } if #ref_name == #field_name => #FQOption::Some(#field_ident) }); enum_field_at.push(quote! { - #unit{ #field_ident, .. } if #ref_index == #reflect_idx => Some(#field_ident) + #unit{ #field_ident, .. } if #ref_index == #reflect_idx => #FQOption::Some(#field_ident) }); enum_index_of.push(quote! { - #unit{ .. } if #ref_name == #field_name => Some(#reflect_idx) + #unit{ .. } if #ref_name == #field_name => #FQOption::Some(#reflect_idx) }); enum_name_at.push(quote! { - #unit{ .. } if #ref_index == #reflect_idx => Some(#field_name) + #unit{ .. } if #ref_index == #reflect_idx => #FQOption::Some(#field_name) }); #[cfg(feature = "documentation")] diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs index b73fbcf1a4b27..eefffd3ec0506 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs @@ -1,11 +1,14 @@ +use crate::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult}; use crate::impls::impl_typed; use crate::ReflectStruct; use proc_macro::TokenStream; -use quote::quote; +use quote::{quote, ToTokens}; use syn::{Index, Member}; /// Implements `Struct`, `GetTypeRegistration`, and `Reflect` for the given derive data. pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream { + let fqoption = FQOption.into_token_stream(); + let bevy_reflect_path = reflect_struct.meta().bevy_reflect_path(); let struct_name = reflect_struct.meta().type_name(); @@ -45,7 +48,7 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream { .get_partial_eq_impl(bevy_reflect_path) .unwrap_or_else(|| { quote! { - fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> Option { + fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> #FQOption { #bevy_reflect_path::struct_partial_eq(self, value) } } @@ -106,38 +109,38 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream { #typed_impl impl #impl_generics #bevy_reflect_path::Struct for #struct_name #ty_generics #where_clause { - fn field(&self, name: &str) -> Option<&dyn #bevy_reflect_path::Reflect> { + fn field(&self, name: &str) -> #FQOption<&dyn #bevy_reflect_path::Reflect> { match name { - #(#field_names => Some(&self.#field_idents),)* - _ => None, + #(#field_names => #fqoption::Some(&self.#field_idents),)* + _ => #FQOption::None, } } - fn field_mut(&mut self, name: &str) -> Option<&mut dyn #bevy_reflect_path::Reflect> { + fn field_mut(&mut self, name: &str) -> #FQOption<&mut dyn #bevy_reflect_path::Reflect> { match name { - #(#field_names => Some(&mut self.#field_idents),)* - _ => None, + #(#field_names => #fqoption::Some(&mut self.#field_idents),)* + _ => #FQOption::None, } } - fn field_at(&self, index: usize) -> Option<&dyn #bevy_reflect_path::Reflect> { + fn field_at(&self, index: usize) -> #FQOption<&dyn #bevy_reflect_path::Reflect> { match index { - #(#field_indices => Some(&self.#field_idents),)* - _ => None, + #(#field_indices => #fqoption::Some(&self.#field_idents),)* + _ => #FQOption::None, } } - fn field_at_mut(&mut self, index: usize) -> Option<&mut dyn #bevy_reflect_path::Reflect> { + fn field_at_mut(&mut self, index: usize) -> #FQOption<&mut dyn #bevy_reflect_path::Reflect> { match index { - #(#field_indices => Some(&mut self.#field_idents),)* - _ => None, + #(#field_indices => #fqoption::Some(&mut self.#field_idents),)* + _ => #FQOption::None, } } - fn name_at(&self, index: usize) -> Option<&str> { + fn name_at(&self, index: usize) -> #FQOption<&str> { match index { - #(#field_indices => Some(#field_names),)* - _ => None, + #(#field_indices => #fqoption::Some(#field_names),)* + _ => #FQOption::None, } } @@ -150,9 +153,9 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream { } fn clone_dynamic(&self) -> #bevy_reflect_path::DynamicStruct { - let mut dynamic = #bevy_reflect_path::DynamicStruct::default(); - dynamic.set_name(self.type_name().to_string()); - #(dynamic.insert_boxed(#field_names, self.#field_idents.clone_value());)* + let mut dynamic: #bevy_reflect_path::DynamicStruct = #FQDefault::default(); + dynamic.set_name(::std::string::ToString::to_string(#bevy_reflect_path::Reflect::type_name(self))); + #(dynamic.insert_boxed(#field_names, #bevy_reflect_path::Reflect::clone_value(&self.#field_idents));)* dynamic } } @@ -160,7 +163,7 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream { impl #impl_generics #bevy_reflect_path::Reflect for #struct_name #ty_generics #where_clause { #[inline] fn type_name(&self) -> &str { - std::any::type_name::() + ::core::any::type_name::() } #[inline] @@ -169,22 +172,22 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream { } #[inline] - fn into_any(self: Box) -> Box { + fn into_any(self: #FQBox) -> #FQBox { self } #[inline] - fn as_any(&self) -> &dyn std::any::Any { + fn as_any(&self) -> &dyn #FQAny { self } #[inline] - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + fn as_any_mut(&mut self) -> &mut dyn #FQAny { self } #[inline] - fn into_reflect(self: Box) -> Box { + fn into_reflect(self: #FQBox) -> #FQBox { self } @@ -199,20 +202,21 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream { } #[inline] - fn clone_value(&self) -> Box { - Box::new(#bevy_reflect_path::Struct::clone_dynamic(self)) + fn clone_value(&self) -> #FQBox { + #FQBox::new(#bevy_reflect_path::Struct::clone_dynamic(self)) } + #[inline] - fn set(&mut self, value: Box) -> Result<(), Box> { - *self = value.take()?; - Ok(()) + fn set(&mut self, value: #FQBox) -> #FQResult<(), #FQBox> { + *self = ::take(value)?; + #FQResult::Ok(()) } #[inline] fn apply(&mut self, value: &dyn #bevy_reflect_path::Reflect) { - if let #bevy_reflect_path::ReflectRef::Struct(struct_value) = value.reflect_ref() { - for (i, value) in struct_value.iter_fields().enumerate() { - let name = struct_value.name_at(i).unwrap(); + if let #bevy_reflect_path::ReflectRef::Struct(struct_value) = #bevy_reflect_path::Reflect::reflect_ref(value) { + for (i, value) in ::core::iter::Iterator::enumerate(#bevy_reflect_path::Struct::iter_fields(struct_value)) { + let name = #bevy_reflect_path::Struct::name_at(struct_value, i).unwrap(); #bevy_reflect_path::Struct::field_mut(self, name).map(|v| v.apply(value)); } } else { @@ -228,7 +232,7 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> TokenStream { #bevy_reflect_path::ReflectMut::Struct(self) } - fn reflect_owned(self: Box) -> #bevy_reflect_path::ReflectOwned { + fn reflect_owned(self: #FQBox) -> #bevy_reflect_path::ReflectOwned { #bevy_reflect_path::ReflectOwned::Struct(self) } diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs index 0c0c233c9a7ff..da187a3ca8a0b 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs @@ -1,11 +1,14 @@ +use crate::fq_std::{FQAny, FQBox, FQDefault, FQOption, FQResult}; use crate::impls::impl_typed; use crate::ReflectStruct; use proc_macro::TokenStream; -use quote::quote; +use quote::{quote, ToTokens}; use syn::{Index, Member}; /// Implements `TupleStruct`, `GetTypeRegistration`, and `Reflect` for the given derive data. pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream { + let fqoption = FQOption.into_token_stream(); + let bevy_reflect_path = reflect_struct.meta().bevy_reflect_path(); let struct_name = reflect_struct.meta().type_name(); let get_type_registration_impl = reflect_struct.get_type_registration(); @@ -29,7 +32,7 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream { .get_partial_eq_impl(bevy_reflect_path) .unwrap_or_else(|| { quote! { - fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> Option { + fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> #FQOption { #bevy_reflect_path::tuple_struct_partial_eq(self, value) } } @@ -89,17 +92,17 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream { #typed_impl impl #impl_generics #bevy_reflect_path::TupleStruct for #struct_name #ty_generics #where_clause { - fn field(&self, index: usize) -> Option<&dyn #bevy_reflect_path::Reflect> { + fn field(&self, index: usize) -> #FQOption<&dyn #bevy_reflect_path::Reflect> { match index { - #(#field_indices => Some(&self.#field_idents),)* - _ => None, + #(#field_indices => #fqoption::Some(&self.#field_idents),)* + _ => #FQOption::None, } } - fn field_mut(&mut self, index: usize) -> Option<&mut dyn #bevy_reflect_path::Reflect> { + fn field_mut(&mut self, index: usize) -> #FQOption<&mut dyn #bevy_reflect_path::Reflect> { match index { - #(#field_indices => Some(&mut self.#field_idents),)* - _ => None, + #(#field_indices => #fqoption::Some(&mut self.#field_idents),)* + _ => #FQOption::None, } } @@ -112,9 +115,9 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream { } fn clone_dynamic(&self) -> #bevy_reflect_path::DynamicTupleStruct { - let mut dynamic = #bevy_reflect_path::DynamicTupleStruct::default(); - dynamic.set_name(self.type_name().to_string()); - #(dynamic.insert_boxed(self.#field_idents.clone_value());)* + let mut dynamic: #bevy_reflect_path::DynamicTupleStruct = #FQDefault::default(); + dynamic.set_name(::std::string::ToString::to_string(#bevy_reflect_path::Reflect::type_name(self))); + #(dynamic.insert_boxed(#bevy_reflect_path::Reflect::clone_value(&self.#field_idents));)* dynamic } } @@ -122,7 +125,7 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream { impl #impl_generics #bevy_reflect_path::Reflect for #struct_name #ty_generics #where_clause { #[inline] fn type_name(&self) -> &str { - std::any::type_name::() + ::core::any::type_name::() } #[inline] @@ -131,22 +134,22 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream { } #[inline] - fn into_any(self: Box) -> Box { + fn into_any(self: #FQBox) -> #FQBox { self } #[inline] - fn as_any(&self) -> &dyn std::any::Any { + fn as_any(&self) -> &dyn #FQAny { self } #[inline] - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + fn as_any_mut(&mut self) -> &mut dyn #FQAny { self } #[inline] - fn into_reflect(self: Box) -> Box { + fn into_reflect(self: #FQBox) -> #FQBox { self } @@ -161,19 +164,20 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream { } #[inline] - fn clone_value(&self) -> Box { - Box::new(#bevy_reflect_path::TupleStruct::clone_dynamic(self)) + fn clone_value(&self) -> #FQBox { + #FQBox::new(#bevy_reflect_path::TupleStruct::clone_dynamic(self)) } + #[inline] - fn set(&mut self, value: Box) -> Result<(), Box> { - *self = value.take()?; - Ok(()) + fn set(&mut self, value: #FQBox) -> #FQResult<(), #FQBox> { + *self = ::take(value)?; + #FQResult::Ok(()) } #[inline] fn apply(&mut self, value: &dyn #bevy_reflect_path::Reflect) { - if let #bevy_reflect_path::ReflectRef::TupleStruct(struct_value) = value.reflect_ref() { - for (i, value) in struct_value.iter_fields().enumerate() { + if let #bevy_reflect_path::ReflectRef::TupleStruct(struct_value) = #bevy_reflect_path::Reflect::reflect_ref(value) { + for (i, value) in ::core::iter::Iterator::enumerate(#bevy_reflect_path::TupleStruct::iter_fields(struct_value)) { #bevy_reflect_path::TupleStruct::field_mut(self, i).map(|v| v.apply(value)); } } else { @@ -189,7 +193,7 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> TokenStream { #bevy_reflect_path::ReflectMut::TupleStruct(self) } - fn reflect_owned(self: Box) -> #bevy_reflect_path::ReflectOwned { + fn reflect_owned(self: #FQBox) -> #bevy_reflect_path::ReflectOwned { #bevy_reflect_path::ReflectOwned::TupleStruct(self) } diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs index 5bf7d3ec77ecc..5d87027d1a25a 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs @@ -1,3 +1,4 @@ +use crate::fq_std::{FQAny, FQBox, FQClone, FQOption, FQResult}; use crate::impls::impl_typed; use crate::ReflectMeta; use proc_macro::TokenStream; @@ -41,7 +42,7 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> TokenStream { impl #impl_generics #bevy_reflect_path::Reflect for #type_name #ty_generics #where_clause { #[inline] fn type_name(&self) -> &str { - std::any::type_name::() + ::core::any::type_name::() } #[inline] @@ -50,22 +51,22 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> TokenStream { } #[inline] - fn into_any(self: Box) -> Box { + fn into_any(self: #FQBox) -> #FQBox { self } #[inline] - fn as_any(&self) -> &dyn std::any::Any { + fn as_any(&self) -> &dyn #FQAny { self } #[inline] - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + fn as_any_mut(&mut self) -> &mut dyn #FQAny { self } #[inline] - fn into_reflect(self: Box) -> Box { + fn into_reflect(self: #FQBox) -> #FQBox { self } @@ -80,24 +81,24 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> TokenStream { } #[inline] - fn clone_value(&self) -> Box { - Box::new(std::clone::Clone::clone(self)) + fn clone_value(&self) -> #FQBox { + #FQBox::new(#FQClone::clone(self)) } #[inline] fn apply(&mut self, value: &dyn #bevy_reflect_path::Reflect) { - let value = value.as_any(); - if let Some(value) = value.downcast_ref::() { - *self = std::clone::Clone::clone(value); + let value = #bevy_reflect_path::Reflect::as_any(value); + if let #FQOption::Some(value) = ::downcast_ref::(value) { + *self = #FQClone::clone(value); } else { - panic!("Value is not {}.", std::any::type_name::()); + panic!("Value is not {}.", ::core::any::type_name::()); } } #[inline] - fn set(&mut self, value: Box) -> Result<(), Box> { - *self = value.take()?; - Ok(()) + fn set(&mut self, value: #FQBox) -> #FQResult<(), #FQBox> { + *self = ::take(value)?; + #FQResult::Ok(()) } fn reflect_ref(&self) -> #bevy_reflect_path::ReflectRef { @@ -108,7 +109,7 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> TokenStream { #bevy_reflect_path::ReflectMut::Value(self) } - fn reflect_owned(self: Box) -> #bevy_reflect_path::ReflectOwned { + fn reflect_owned(self: #FQBox) -> #bevy_reflect_path::ReflectOwned { #bevy_reflect_path::ReflectOwned::Value(self) } diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs b/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs index 33d9ce3b75061..eb486d86baa31 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs @@ -20,6 +20,7 @@ mod derive_data; mod documentation; mod enum_utility; mod field_attributes; +mod fq_std; mod from_reflect; mod impls; mod reflect_value; diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs b/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs index bf05718c67d0b..456e4152ca85f 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs @@ -17,7 +17,7 @@ pub(crate) fn impl_get_type_registration( let serialization_data = serialization_denylist.map(|denylist| { let denylist = denylist.into_iter(); quote! { - let ignored_indices = [#(#denylist),*].into_iter(); + let ignored_indices = ::core::iter::IntoIterator::into_iter([#(#denylist),*]); registration.insert::<#bevy_reflect_path::serde::SerializationData>(#bevy_reflect_path::serde::SerializationData::new(ignored_indices)); } }); diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/trait_reflection.rs b/crates/bevy_reflect/bevy_reflect_derive/src/trait_reflection.rs index 7d16bfb01c85a..5441fa3c1f8da 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/trait_reflection.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/trait_reflection.rs @@ -1,3 +1,4 @@ +use crate::fq_std::{FQBox, FQClone, FQOption, FQResult}; use bevy_macro_utils::BevyManifest; use proc_macro::TokenStream; use quote::quote; @@ -55,26 +56,26 @@ pub(crate) fn reflect_trait(_args: &TokenStream, input: TokenStream) -> TokenStr #item_trait #[doc = #struct_doc] - #[derive(Clone)] + #[derive(#FQClone)] #trait_vis struct #reflect_trait_ident { - get_func: fn(&dyn #bevy_reflect_path::Reflect) -> Option<&dyn #trait_ident>, - get_mut_func: fn(&mut dyn #bevy_reflect_path::Reflect) -> Option<&mut dyn #trait_ident>, - get_boxed_func: fn(Box) -> Result, Box>, + get_func: fn(&dyn #bevy_reflect_path::Reflect) -> #FQOption<&dyn #trait_ident>, + get_mut_func: fn(&mut dyn #bevy_reflect_path::Reflect) -> #FQOption<&mut dyn #trait_ident>, + get_boxed_func: fn(#FQBox) -> #FQResult<#FQBox, #FQBox>, } impl #reflect_trait_ident { #[doc = #get_doc] - pub fn get<'a>(&self, reflect_value: &'a dyn #bevy_reflect_path::Reflect) -> Option<&'a dyn #trait_ident> { + pub fn get<'a>(&self, reflect_value: &'a dyn #bevy_reflect_path::Reflect) -> #FQOption<&'a dyn #trait_ident> { (self.get_func)(reflect_value) } #[doc = #get_mut_doc] - pub fn get_mut<'a>(&self, reflect_value: &'a mut dyn #bevy_reflect_path::Reflect) -> Option<&'a mut dyn #trait_ident> { + pub fn get_mut<'a>(&self, reflect_value: &'a mut dyn #bevy_reflect_path::Reflect) -> #FQOption<&'a mut dyn #trait_ident> { (self.get_mut_func)(reflect_value) } #[doc = #get_box_doc] - pub fn get_boxed(&self, reflect_value: Box) -> Result, Box> { + pub fn get_boxed(&self, reflect_value: #FQBox) -> #FQResult<#FQBox, #FQBox> { (self.get_boxed_func)(reflect_value) } } @@ -83,13 +84,13 @@ pub(crate) fn reflect_trait(_args: &TokenStream, input: TokenStream) -> TokenStr fn from_type() -> Self { Self { get_func: |reflect_value| { - reflect_value.downcast_ref::().map(|value| value as &dyn #trait_ident) + ::downcast_ref::(reflect_value).map(|value| value as &dyn #trait_ident) }, get_mut_func: |reflect_value| { - reflect_value.downcast_mut::().map(|value| value as &mut dyn #trait_ident) + ::downcast_mut::(reflect_value).map(|value| value as &mut dyn #trait_ident) }, get_boxed_func: |reflect_value| { - reflect_value.downcast::().map(|value| value as Box) + ::downcast::(reflect_value).map(|value| value as #FQBox) } } } diff --git a/crates/bevy_reflect/src/from_reflect.rs b/crates/bevy_reflect/src/from_reflect.rs new file mode 100644 index 0000000000000..e9df08aefc459 --- /dev/null +++ b/crates/bevy_reflect/src/from_reflect.rs @@ -0,0 +1,92 @@ +use crate::{FromType, Reflect}; + +/// A trait for types which can be constructed from a reflected type. +/// +/// This trait can be derived on types which implement [`Reflect`]. Some complex +/// types (such as `Vec`) may only be reflected if their element types +/// implement this trait. +/// +/// For structs and tuple structs, fields marked with the `#[reflect(ignore)]` +/// attribute will be constructed using the `Default` implementation of the +/// field type, rather than the corresponding field value (if any) of the +/// reflected value. +pub trait FromReflect: Reflect + Sized { + /// Constructs a concrete instance of `Self` from a reflected value. + fn from_reflect(reflect: &dyn Reflect) -> Option; +} + +/// Type data that represents the [`FromReflect`] trait and allows it to be used dynamically. +/// +/// `FromReflect` allows dynamic types (e.g. [`DynamicStruct`], [`DynamicEnum`], etc.) to be converted +/// to their full, concrete types. This is most important when it comes to deserialization where it isn't +/// guaranteed that every field exists when trying to construct the final output. +/// +/// However, to do this, you normally need to specify the exact concrete type: +/// +/// ``` +/// # use bevy_reflect::{DynamicTupleStruct, FromReflect, Reflect}; +/// #[derive(Reflect, FromReflect, PartialEq, Eq, Debug)] +/// struct Foo(#[reflect(default = "default_value")] usize); +/// +/// fn default_value() -> usize { 123 } +/// +/// let reflected = DynamicTupleStruct::default(); +/// +/// let concrete: Foo = ::from_reflect(&reflected).unwrap(); +/// +/// assert_eq!(Foo(123), concrete); +/// ``` +/// +/// In a dynamic context where the type might not be known at compile-time, this is nearly impossible to do. +/// That is why this type data struct exists— it allows us to construct the full type without knowing +/// what the actual type is. +/// +/// # Example +/// +/// ``` +/// # use bevy_reflect::{DynamicTupleStruct, FromReflect, Reflect, ReflectFromReflect, TypeRegistry}; +/// # #[derive(Reflect, FromReflect, PartialEq, Eq, Debug)] +/// # #[reflect(FromReflect)] +/// # struct Foo(#[reflect(default = "default_value")] usize); +/// # fn default_value() -> usize { 123 } +/// # let mut registry = TypeRegistry::new(); +/// # registry.register::(); +/// +/// let mut reflected = DynamicTupleStruct::default(); +/// reflected.set_name(std::any::type_name::().to_string()); +/// +/// let registration = registry.get_with_name(reflected.type_name()).unwrap(); +/// let rfr = registration.data::().unwrap(); +/// +/// let concrete: Box = rfr.from_reflect(&reflected).unwrap(); +/// +/// assert_eq!(Foo(123), concrete.take::().unwrap()); +/// ``` +/// +/// [`DynamicStruct`]: crate::DynamicStruct +/// [`DynamicEnum`]: crate::DynamicEnum +#[derive(Clone)] +pub struct ReflectFromReflect { + from_reflect: fn(&dyn Reflect) -> Option>, +} + +impl ReflectFromReflect { + /// Perform a [`FromReflect::from_reflect`] conversion on the given reflection object. + /// + /// This will convert the object to a concrete type if it wasn't already, and return + /// the value as `Box`. + #[allow(clippy::wrong_self_convention)] + pub fn from_reflect(&self, reflect_value: &dyn Reflect) -> Option> { + (self.from_reflect)(reflect_value) + } +} + +impl FromType for ReflectFromReflect { + fn from_type() -> Self { + Self { + from_reflect: |reflect_value| { + T::from_reflect(reflect_value).map(|value| Box::new(value) as Box) + }, + } + } +} diff --git a/crates/bevy_reflect/src/impls/glam.rs b/crates/bevy_reflect/src/impls/glam.rs index 739148f044cdc..bb3ab96e3a365 100644 --- a/crates/bevy_reflect/src/impls/glam.rs +++ b/crates/bevy_reflect/src/impls/glam.rs @@ -1,19 +1,18 @@ use crate as bevy_reflect; use crate::prelude::ReflectDefault; -use crate::reflect::Reflect; use crate::{ReflectDeserialize, ReflectSerialize}; use bevy_reflect_derive::{impl_from_reflect_value, impl_reflect_struct, impl_reflect_value}; use glam::*; impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Hash, PartialEq, Default)] struct IVec2 { x: i32, y: i32, } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Hash, PartialEq, Default)] struct IVec3 { x: i32, y: i32, @@ -21,7 +20,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Hash, PartialEq, Default)] struct IVec4 { x: i32, y: i32, @@ -31,14 +30,14 @@ impl_reflect_struct!( ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Hash, PartialEq, Default)] struct UVec2 { x: u32, y: u32, } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Hash, PartialEq, Default)] struct UVec3 { x: u32, y: u32, @@ -46,7 +45,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Hash, PartialEq, Default)] struct UVec4 { x: u32, y: u32, @@ -139,14 +138,14 @@ impl_reflect_struct!( ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Default)] struct Mat2 { x_axis: Vec2, y_axis: Vec2, } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Default)] struct Mat3 { x_axis: Vec3, y_axis: Vec3, @@ -154,7 +153,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Default)] struct Mat3A { x_axis: Vec3A, y_axis: Vec3A, @@ -162,7 +161,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Default)] struct Mat4 { x_axis: Vec4, y_axis: Vec4, @@ -172,14 +171,14 @@ impl_reflect_struct!( ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Default)] struct DMat2 { x_axis: DVec2, y_axis: DVec2, } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Default)] struct DMat3 { x_axis: DVec3, y_axis: DVec3, @@ -187,7 +186,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Default)] struct DMat4 { x_axis: DVec4, y_axis: DVec4, @@ -197,14 +196,14 @@ impl_reflect_struct!( ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Default)] struct Affine2 { matrix2: Mat2, translation: Vec2, } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Default)] struct Affine3A { matrix3: Mat3A, translation: Vec3A, @@ -212,14 +211,14 @@ impl_reflect_struct!( ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Default)] struct DAffine2 { matrix2: DMat2, translation: DVec2, } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, PartialEq, Default)] struct DAffine3 { matrix3: DMat3, translation: DVec3, diff --git a/crates/bevy_reflect/src/impls/rect.rs b/crates/bevy_reflect/src/impls/rect.rs index f134e9cfe0627..9db5e1cd85196 100644 --- a/crates/bevy_reflect/src/impls/rect.rs +++ b/crates/bevy_reflect/src/impls/rect.rs @@ -1,6 +1,5 @@ use crate as bevy_reflect; use crate::prelude::ReflectDefault; -use crate::reflect::Reflect; use crate::{ReflectDeserialize, ReflectSerialize}; use bevy_math::{Rect, Vec2}; use bevy_reflect_derive::impl_reflect_struct; diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index ce56cef43f219..ca7ad9c8710e9 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -12,18 +12,18 @@ use crate::utility::{GenericTypeInfoCell, NonGenericTypeInfoCell}; use bevy_reflect_derive::{impl_from_reflect_value, impl_reflect_value}; use bevy_utils::{Duration, Instant}; use bevy_utils::{HashMap, HashSet}; -#[cfg(any(unix, windows))] -use std::ffi::OsString; use std::{ any::Any, borrow::Cow, + collections::VecDeque, + ffi::OsString, hash::{Hash, Hasher}, num::{ NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, }, ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}, - path::PathBuf, + path::{Path, PathBuf}, }; impl_reflect_value!(bool( @@ -130,10 +130,12 @@ impl_reflect_value!(NonZeroU16(Debug, Hash, PartialEq, Serialize, Deserialize)); impl_reflect_value!(NonZeroU8(Debug, Hash, PartialEq, Serialize, Deserialize)); impl_reflect_value!(NonZeroI8(Debug, Hash, PartialEq, Serialize, Deserialize)); -// Only for platforms that can serialize it as in serde: +// `Serialize` and `Deserialize` only for platforms supported by serde: // https://github.com/serde-rs/serde/blob/3ffb86fc70efd3d329519e2dddfa306cc04f167c/serde/src/de/impls.rs#L1732 #[cfg(any(unix, windows))] impl_reflect_value!(OsString(Debug, Hash, PartialEq, Serialize, Deserialize)); +#[cfg(not(any(unix, windows)))] +impl_reflect_value!(OsString(Debug, Hash, PartialEq)); impl_from_reflect_value!(bool); impl_from_reflect_value!(char); @@ -152,6 +154,8 @@ impl_from_reflect_value!(isize); impl_from_reflect_value!(f32); impl_from_reflect_value!(f64); impl_from_reflect_value!(String); +impl_from_reflect_value!(PathBuf); +impl_from_reflect_value!(OsString); impl_from_reflect_value!(HashSet); impl_from_reflect_value!(Range); impl_from_reflect_value!(RangeInclusive); @@ -174,152 +178,164 @@ impl_from_reflect_value!(NonZeroU16); impl_from_reflect_value!(NonZeroU8); impl_from_reflect_value!(NonZeroI8); -impl Array for Vec { - #[inline] - fn get(&self, index: usize) -> Option<&dyn Reflect> { - <[T]>::get(self, index).map(|value| value as &dyn Reflect) - } +macro_rules! impl_reflect_for_veclike { + ($ty:ty, $push:expr, $pop:expr, $sub:ty) => { + impl Array for $ty { + #[inline] + fn get(&self, index: usize) -> Option<&dyn Reflect> { + <$sub>::get(self, index).map(|value| value as &dyn Reflect) + } - #[inline] - fn get_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> { - <[T]>::get_mut(self, index).map(|value| value as &mut dyn Reflect) - } + #[inline] + fn get_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> { + <$sub>::get_mut(self, index).map(|value| value as &mut dyn Reflect) + } - #[inline] - fn len(&self) -> usize { - <[T]>::len(self) - } + #[inline] + fn len(&self) -> usize { + <$sub>::len(self) + } - #[inline] - fn iter(&self) -> ArrayIter { - ArrayIter { - array: self, - index: 0, - } - } + #[inline] + fn iter(&self) -> ArrayIter { + ArrayIter { + array: self, + index: 0, + } + } - #[inline] - fn drain(self: Box) -> Vec> { - self.into_iter() - .map(|value| Box::new(value) as Box) - .collect() - } -} + #[inline] + fn drain(self: Box) -> Vec> { + self.into_iter() + .map(|value| Box::new(value) as Box) + .collect() + } + } -impl List for Vec { - fn push(&mut self, value: Box) { - let value = value.take::().unwrap_or_else(|value| { - T::from_reflect(&*value).unwrap_or_else(|| { - panic!( - "Attempted to push invalid value of type {}.", - value.type_name() - ) - }) - }); - Vec::push(self, value); - } + impl List for $ty { + fn push(&mut self, value: Box) { + let value = value.take::().unwrap_or_else(|value| { + T::from_reflect(&*value).unwrap_or_else(|| { + panic!( + "Attempted to push invalid value of type {}.", + value.type_name() + ) + }) + }); + $push(self, value); + } - fn pop(&mut self) -> Option> { - self.pop().map(|value| Box::new(value) as Box) - } -} + fn pop(&mut self) -> Option> { + $pop(self).map(|value| Box::new(value) as Box) + } + } -impl Reflect for Vec { - fn type_name(&self) -> &str { - std::any::type_name::() - } + impl Reflect for $ty { + fn type_name(&self) -> &str { + std::any::type_name::() + } - fn get_type_info(&self) -> &'static TypeInfo { - ::type_info() - } + fn get_type_info(&self) -> &'static TypeInfo { + ::type_info() + } - fn into_any(self: Box) -> Box { - self - } + fn into_any(self: Box) -> Box { + self + } - fn as_any(&self) -> &dyn Any { - self - } + fn as_any(&self) -> &dyn Any { + self + } - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } - fn into_reflect(self: Box) -> Box { - self - } + fn into_reflect(self: Box) -> Box { + self + } - fn as_reflect(&self) -> &dyn Reflect { - self - } + fn as_reflect(&self) -> &dyn Reflect { + self + } - fn as_reflect_mut(&mut self) -> &mut dyn Reflect { - self - } + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + self + } - fn apply(&mut self, value: &dyn Reflect) { - crate::list_apply(self, value); - } + fn apply(&mut self, value: &dyn Reflect) { + crate::list_apply(self, value); + } - fn set(&mut self, value: Box) -> Result<(), Box> { - *self = value.take()?; - Ok(()) - } + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::List(self) - } + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::List(self) + } - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::List(self) - } + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::List(self) + } - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::List(self) - } + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::List(self) + } - fn clone_value(&self) -> Box { - Box::new(List::clone_dynamic(self)) - } + fn clone_value(&self) -> Box { + Box::new(List::clone_dynamic(self)) + } - fn reflect_hash(&self) -> Option { - crate::array_hash(self) - } + fn reflect_hash(&self) -> Option { + crate::array_hash(self) + } - fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { - crate::list_partial_eq(self, value) - } -} + fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { + crate::list_partial_eq(self, value) + } + } -impl Typed for Vec { - fn type_info() -> &'static TypeInfo { - static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); - CELL.get_or_insert::(|| TypeInfo::List(ListInfo::new::())) - } -} + impl Typed for $ty { + fn type_info() -> &'static TypeInfo { + static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); + CELL.get_or_insert::(|| TypeInfo::List(ListInfo::new::())) + } + } -impl GetTypeRegistration for Vec { - fn get_type_registration() -> TypeRegistration { - let mut registration = TypeRegistration::of::>(); - registration.insert::(FromType::>::from_type()); - registration - } -} + impl GetTypeRegistration for $ty { + fn get_type_registration() -> TypeRegistration { + let mut registration = TypeRegistration::of::>(); + registration.insert::(FromType::>::from_type()); + registration + } + } -impl FromReflect for Vec { - fn from_reflect(reflect: &dyn Reflect) -> Option { - if let ReflectRef::List(ref_list) = reflect.reflect_ref() { - let mut new_list = Self::with_capacity(ref_list.len()); - for field in ref_list.iter() { - new_list.push(T::from_reflect(field)?); + impl FromReflect for $ty { + fn from_reflect(reflect: &dyn Reflect) -> Option { + if let ReflectRef::List(ref_list) = reflect.reflect_ref() { + let mut new_list = Self::with_capacity(ref_list.len()); + for field in ref_list.iter() { + $push(&mut new_list, T::from_reflect(field)?); + } + Some(new_list) + } else { + None + } } - Some(new_list) - } else { - None } - } + }; } +impl_reflect_for_veclike!(Vec, Vec::push, Vec::pop, [T]); +impl_reflect_for_veclike!( + VecDeque, + VecDeque::push_back, + VecDeque::pop_back, + VecDeque:: +); + impl Map for HashMap { fn get(&self, key: &dyn Reflect) -> Option<&dyn Reflect> { key.downcast_ref::() @@ -665,86 +681,6 @@ impl_array_get_type_registration! { 30 31 32 } -impl Reflect for Cow<'static, str> { - fn type_name(&self) -> &str { - std::any::type_name::() - } - - fn get_type_info(&self) -> &'static TypeInfo { - ::type_info() - } - - fn into_any(self: Box) -> Box { - self - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - fn into_reflect(self: Box) -> Box { - self - } - - fn as_reflect(&self) -> &dyn Reflect { - self - } - - fn as_reflect_mut(&mut self) -> &mut dyn Reflect { - self - } - - fn apply(&mut self, value: &dyn Reflect) { - let value = value.as_any(); - if let Some(value) = value.downcast_ref::() { - *self = value.clone(); - } else { - panic!("Value is not a {}.", std::any::type_name::()); - } - } - - fn set(&mut self, value: Box) -> Result<(), Box> { - *self = value.take()?; - Ok(()) - } - - fn reflect_ref(&self) -> ReflectRef { - ReflectRef::Value(self) - } - - fn reflect_mut(&mut self) -> ReflectMut { - ReflectMut::Value(self) - } - - fn reflect_owned(self: Box) -> ReflectOwned { - ReflectOwned::Value(self) - } - - fn clone_value(&self) -> Box { - Box::new(self.clone()) - } - - fn reflect_hash(&self) -> Option { - let mut hasher = crate::ReflectHasher::default(); - Hash::hash(&std::any::Any::type_id(self), &mut hasher); - Hash::hash(self, &mut hasher); - Some(hasher.finish()) - } - - fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { - let value = value.as_any(); - if let Some(value) = value.downcast_ref::() { - Some(std::cmp::PartialEq::eq(self, value)) - } else { - Some(false) - } - } -} - impl GetTypeRegistration for Option { fn get_type_registration() -> TypeRegistration { TypeRegistration::of::>() @@ -991,6 +927,86 @@ impl Typed for Option { } } +impl Reflect for Cow<'static, str> { + fn type_name(&self) -> &str { + std::any::type_name::() + } + + fn get_type_info(&self) -> &'static TypeInfo { + ::type_info() + } + + fn into_any(self: Box) -> Box { + self + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn into_reflect(self: Box) -> Box { + self + } + + fn as_reflect(&self) -> &dyn Reflect { + self + } + + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + self + } + + fn apply(&mut self, value: &dyn Reflect) { + let value = value.as_any(); + if let Some(value) = value.downcast_ref::() { + *self = value.clone(); + } else { + panic!("Value is not a {}.", std::any::type_name::()); + } + } + + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } + + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Value(self) + } + + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Value(self) + } + + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Value(self) + } + + fn clone_value(&self) -> Box { + Box::new(self.clone()) + } + + fn reflect_hash(&self) -> Option { + let mut hasher = crate::ReflectHasher::default(); + Hash::hash(&std::any::Any::type_id(self), &mut hasher); + Hash::hash(self, &mut hasher); + Some(hasher.finish()) + } + + fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { + let value = value.as_any(); + if let Some(value) = value.downcast_ref::() { + Some(std::cmp::PartialEq::eq(self, value)) + } else { + Some(false) + } + } +} + impl Typed for Cow<'static, str> { fn type_info() -> &'static TypeInfo { static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); @@ -1019,6 +1035,107 @@ impl FromReflect for Cow<'static, str> { } } +impl Reflect for &'static Path { + fn type_name(&self) -> &str { + std::any::type_name::() + } + + fn get_type_info(&self) -> &'static TypeInfo { + ::type_info() + } + + fn into_any(self: Box) -> Box { + self + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn into_reflect(self: Box) -> Box { + self + } + + fn as_reflect(&self) -> &dyn Reflect { + self + } + + fn as_reflect_mut(&mut self) -> &mut dyn Reflect { + self + } + + fn apply(&mut self, value: &dyn Reflect) { + let value = value.as_any(); + if let Some(&value) = value.downcast_ref::() { + *self = value; + } else { + panic!("Value is not a {}.", std::any::type_name::()); + } + } + + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } + + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Value(self) + } + + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Value(self) + } + + fn reflect_owned(self: Box) -> ReflectOwned { + ReflectOwned::Value(self) + } + + fn clone_value(&self) -> Box { + Box::new(*self) + } + + fn reflect_hash(&self) -> Option { + let mut hasher = crate::ReflectHasher::default(); + Hash::hash(&std::any::Any::type_id(self), &mut hasher); + Hash::hash(self, &mut hasher); + Some(hasher.finish()) + } + + fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { + let value = value.as_any(); + if let Some(value) = value.downcast_ref::() { + Some(std::cmp::PartialEq::eq(self, value)) + } else { + Some(false) + } + } +} + +impl Typed for &'static Path { + fn type_info() -> &'static TypeInfo { + static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); + CELL.get_or_set(|| TypeInfo::Value(ValueInfo::new::())) + } +} + +impl GetTypeRegistration for &'static Path { + fn get_type_registration() -> TypeRegistration { + let mut registration = TypeRegistration::of::(); + registration.insert::(FromType::::from_type()); + registration + } +} + +impl FromReflect for &'static Path { + fn from_reflect(reflect: &dyn crate::Reflect) -> Option { + reflect.as_any().downcast_ref::().copied() + } +} + #[cfg(test)] mod tests { use crate as bevy_reflect; @@ -1029,6 +1146,7 @@ mod tests { use bevy_utils::HashMap; use bevy_utils::{Duration, Instant}; use std::f32::consts::{PI, TAU}; + use std::path::Path; #[test] fn can_serialize_duration() { @@ -1228,4 +1346,11 @@ mod tests { let output = ::from_reflect(&expected).unwrap(); assert_eq!(expected, output); } + + #[test] + fn path_should_from_reflect() { + let path = Path::new("hello_world.rs"); + let output = <&'static Path as FromReflect>::from_reflect(&path).unwrap(); + assert_eq!(path, output); + } } diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 6c1c3c8dc320e..dcf2d85861722 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -2,6 +2,7 @@ mod array; mod fields; +mod from_reflect; mod list; mod map; mod path; @@ -47,6 +48,7 @@ pub mod prelude { pub use array::*; pub use enums::*; pub use fields::*; +pub use from_reflect::*; pub use impls::*; pub use list::*; pub use map::*; @@ -103,6 +105,7 @@ mod tests { ser::{to_string_pretty, PrettyConfig}, Deserializer, }; + use std::any::TypeId; use std::fmt::{Debug, Formatter}; use super::prelude::*; @@ -244,6 +247,42 @@ mod tests { assert_eq!(values, vec![1]); } + #[test] + fn should_call_from_reflect_dynamically() { + #[derive(Reflect, FromReflect)] + #[reflect(FromReflect)] + struct MyStruct { + foo: usize, + } + + // Register + let mut registry = TypeRegistry::default(); + registry.register::(); + + // Get type data + let type_id = TypeId::of::(); + let rfr = registry + .get_type_data::(type_id) + .expect("the FromReflect trait should be registered"); + + // Call from_reflect + let mut dynamic_struct = DynamicStruct::default(); + dynamic_struct.insert("foo", 123usize); + let reflected = rfr + .from_reflect(&dynamic_struct) + .expect("the type should be properly reflected"); + + // Assert + let expected = MyStruct { foo: 123 }; + assert!(expected + .reflect_partial_eq(reflected.as_ref()) + .unwrap_or_default()); + let not_expected = MyStruct { foo: 321 }; + assert!(!not_expected + .reflect_partial_eq(reflected.as_ref()) + .unwrap_or_default()); + } + #[test] fn from_reflect_should_use_default_field_attributes() { #[derive(Reflect, FromReflect, Eq, PartialEq, Debug)] diff --git a/crates/bevy_reflect/src/list.rs b/crates/bevy_reflect/src/list.rs index 7e139d48b05e6..6a2c2a8767381 100644 --- a/crates/bevy_reflect/src/list.rs +++ b/crates/bevy_reflect/src/list.rs @@ -11,11 +11,15 @@ use crate::{ /// /// This is a sub-trait of [`Array`] as it implements a [`push`](List::push) function, allowing /// it's internal size to grow. +/// +/// This trait expects index 0 to contain the _front_ element. +/// The _back_ element must refer to the element with the largest index. +/// These two rules above should be upheld by manual implementors. pub trait List: Reflect + Array { - /// Appends an element to the list. + /// Appends an element to the _back_ of the list. fn push(&mut self, value: Box); - /// Removes the last element from the list (highest index in the array) and returns it, or [`None`] if it is empty. + /// Removes the _back_ element from the list and returns it, or [`None`] if it is empty. fn pop(&mut self) -> Option>; /// Clones the list, producing a [`DynamicList`]. diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index 0ba2e5d9eb475..3857d8f3cd84b 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -215,21 +215,6 @@ pub trait Reflect: Any + Send + Sync { } } -/// A trait for types which can be constructed from a reflected type. -/// -/// This trait can be derived on types which implement [`Reflect`]. Some complex -/// types (such as `Vec`) may only be reflected if their element types -/// implement this trait. -/// -/// For structs and tuple structs, fields marked with the `#[reflect(ignore)]` -/// attribute will be constructed using the `Default` implementation of the -/// field type, rather than the corresponding field value (if any) of the -/// reflected value. -pub trait FromReflect: Reflect + Sized { - /// Constructs a concrete instance of `Self` from a reflected value. - fn from_reflect(reflect: &dyn Reflect) -> Option; -} - impl Debug for dyn Reflect { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.debug(f) @@ -280,6 +265,8 @@ impl dyn Reflect { /// a different type, like the Dynamic\*\*\* types do, you can call `represents` /// to determine what type they represent. Represented types cannot be downcasted /// to, but you can use [`FromReflect`] to create a value of the represented type from them. + /// + /// [`FromReflect`]: crate::FromReflect #[inline] pub fn is(&self) -> bool { self.type_id() == TypeId::of::() diff --git a/crates/bevy_reflect/src/serde/de.rs b/crates/bevy_reflect/src/serde/de.rs index 4f6c0f31f7517..656d4b3f55ba8 100644 --- a/crates/bevy_reflect/src/serde/de.rs +++ b/crates/bevy_reflect/src/serde/de.rs @@ -341,6 +341,7 @@ impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> { struct_info.field_names(), StructVisitor { struct_info, + registration: self.registration, registry: self.registry, }, )?; @@ -411,6 +412,7 @@ impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> { enum_info.variant_names(), EnumVisitor { enum_info, + registration: self.registration, registry: self.registry, }, )? @@ -439,6 +441,7 @@ impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> { struct StructVisitor<'a> { struct_info: &'static StructInfo, + registration: &'a TypeRegistration, registry: &'a TypeRegistry, } @@ -463,6 +466,18 @@ impl<'a, 'de> Visitor<'de> for StructVisitor<'a> { let mut index = 0usize; let mut output = DynamicStruct::default(); + let ignored_len = self + .registration + .data::() + .map(|data| data.len()) + .unwrap_or(0); + let field_len = self.struct_info.field_len().saturating_sub(ignored_len); + + if field_len == 0 { + // Handle unit structs and ignored fields + return Ok(output); + } + while let Some(value) = seq.next_element_seed(TypedReflectDeserializer { registration: self .struct_info @@ -501,6 +516,21 @@ impl<'a, 'de> Visitor<'de> for TupleStructVisitor<'a> { let mut index = 0usize; let mut tuple_struct = DynamicTupleStruct::default(); + let ignored_len = self + .registration + .data::() + .map(|data| data.len()) + .unwrap_or(0); + let field_len = self + .tuple_struct_info + .field_len() + .saturating_sub(ignored_len); + + if field_len == 0 { + // Handle unit structs and ignored fields + return Ok(tuple_struct); + } + let get_field_registration = |index: usize| -> Result<&'a TypeRegistration, V::Error> { let field = self.tuple_struct_info.field_at(index).ok_or_else(|| { de::Error::custom(format_args!( @@ -675,6 +705,7 @@ impl<'a, 'de> Visitor<'de> for MapVisitor<'a> { struct EnumVisitor<'a> { enum_info: &'static EnumInfo, + registration: &'a TypeRegistration, registry: &'a TypeRegistry, } @@ -701,6 +732,7 @@ impl<'a, 'de> Visitor<'de> for EnumVisitor<'a> { struct_info.field_names(), StructVariantVisitor { struct_info, + registration: self.registration, registry: self.registry, }, )? @@ -722,6 +754,7 @@ impl<'a, 'de> Visitor<'de> for EnumVisitor<'a> { tuple_info.field_len(), TupleVariantVisitor { tuple_info, + registration: self.registration, registry: self.registry, }, )? @@ -787,6 +820,7 @@ impl<'de> DeserializeSeed<'de> for VariantDeserializer { struct StructVariantVisitor<'a> { struct_info: &'static StructVariantInfo, + registration: &'a TypeRegistration, registry: &'a TypeRegistry, } @@ -811,6 +845,18 @@ impl<'a, 'de> Visitor<'de> for StructVariantVisitor<'a> { let mut index = 0usize; let mut output = DynamicStruct::default(); + let ignored_len = self + .registration + .data::() + .map(|data| data.len()) + .unwrap_or(0); + let field_len = self.struct_info.field_len().saturating_sub(ignored_len); + + if field_len == 0 { + // Handle all fields being ignored + return Ok(output); + } + while let Some(value) = seq.next_element_seed(TypedReflectDeserializer { registration: self .struct_info @@ -831,6 +877,7 @@ impl<'a, 'de> Visitor<'de> for StructVariantVisitor<'a> { struct TupleVariantVisitor<'a> { tuple_info: &'static TupleVariantInfo, + registration: &'a TypeRegistration, registry: &'a TypeRegistry, } @@ -845,6 +892,18 @@ impl<'a, 'de> Visitor<'de> for TupleVariantVisitor<'a> { where V: SeqAccess<'de>, { + let ignored_len = self + .registration + .data::() + .map(|data| data.len()) + .unwrap_or(0); + let field_len = self.tuple_info.field_len().saturating_sub(ignored_len); + + if field_len == 0 { + // Handle all fields being ignored + return Ok(DynamicTuple::default()); + } + visit_tuple(&mut seq, self.tuple_info, self.registry) } } @@ -1011,10 +1070,15 @@ mod tests { map_value: HashMap, struct_value: SomeStruct, tuple_struct_value: SomeTupleStruct, + unit_struct: SomeUnitStruct, unit_enum: SomeEnum, newtype_enum: SomeEnum, tuple_enum: SomeEnum, struct_enum: SomeEnum, + ignored_struct: SomeIgnoredStruct, + ignored_tuple_struct: SomeIgnoredTupleStruct, + ignored_struct_variant: SomeIgnoredEnum, + ignored_tuple_variant: SomeIgnoredEnum, custom_deserialize: CustomDeserialize, } @@ -1026,6 +1090,18 @@ mod tests { #[derive(Reflect, FromReflect, Debug, PartialEq)] struct SomeTupleStruct(String); + #[derive(Reflect, FromReflect, Debug, PartialEq)] + struct SomeUnitStruct; + + #[derive(Reflect, FromReflect, Debug, PartialEq)] + struct SomeIgnoredStruct { + #[reflect(ignore)] + ignored: i32, + } + + #[derive(Reflect, FromReflect, Debug, PartialEq)] + struct SomeIgnoredTupleStruct(#[reflect(ignore)] i32); + #[derive(Reflect, FromReflect, Debug, PartialEq, Deserialize)] struct SomeDeserializableStruct { foo: i64, @@ -1050,14 +1126,27 @@ mod tests { Struct { foo: String }, } + #[derive(Reflect, FromReflect, Debug, PartialEq)] + enum SomeIgnoredEnum { + Tuple(#[reflect(ignore)] f32, #[reflect(ignore)] f32), + Struct { + #[reflect(ignore)] + foo: String, + }, + } + fn get_registry() -> TypeRegistry { let mut registry = TypeRegistry::default(); registry.register::(); registry.register::(); registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); registry.register::(); registry.register::(); registry.register::(); + registry.register::(); registry.register::(); registry.register::(); registry.register::(); @@ -1090,12 +1179,19 @@ mod tests { map_value: map, struct_value: SomeStruct { foo: 999999999 }, tuple_struct_value: SomeTupleStruct(String::from("Tuple Struct")), + unit_struct: SomeUnitStruct, unit_enum: SomeEnum::Unit, newtype_enum: SomeEnum::NewType(123), tuple_enum: SomeEnum::Tuple(1.23, 3.21), struct_enum: SomeEnum::Struct { foo: String::from("Struct variant value"), }, + ignored_struct: SomeIgnoredStruct { ignored: 0 }, + ignored_tuple_struct: SomeIgnoredTupleStruct(0), + ignored_struct_variant: SomeIgnoredEnum::Struct { + foo: String::default(), + }, + ignored_tuple_variant: SomeIgnoredEnum::Tuple(0.0, 0.0), custom_deserialize: CustomDeserialize { value: 100, inner_struct: SomeDeserializableStruct { foo: 101 }, @@ -1125,12 +1221,17 @@ mod tests { foo: 999999999, ), tuple_struct_value: ("Tuple Struct"), + unit_struct: (), unit_enum: Unit, newtype_enum: NewType(123), tuple_enum: Tuple(1.23, 3.21), struct_enum: Struct( foo: "Struct variant value", ), + ignored_struct: (), + ignored_tuple_struct: (), + ignored_struct_variant: Struct(), + ignored_tuple_variant: Tuple(), custom_deserialize: ( value: 100, renamed: ( @@ -1337,12 +1438,19 @@ mod tests { map_value: map, struct_value: SomeStruct { foo: 999999999 }, tuple_struct_value: SomeTupleStruct(String::from("Tuple Struct")), + unit_struct: SomeUnitStruct, unit_enum: SomeEnum::Unit, newtype_enum: SomeEnum::NewType(123), tuple_enum: SomeEnum::Tuple(1.23, 3.21), struct_enum: SomeEnum::Struct { foo: String::from("Struct variant value"), }, + ignored_struct: SomeIgnoredStruct { ignored: 0 }, + ignored_tuple_struct: SomeIgnoredTupleStruct(0), + ignored_struct_variant: SomeIgnoredEnum::Struct { + foo: String::default(), + }, + ignored_tuple_variant: SomeIgnoredEnum::Tuple(0.0, 0.0), custom_deserialize: CustomDeserialize { value: 100, inner_struct: SomeDeserializableStruct { foo: 101 }, @@ -1363,7 +1471,8 @@ mod tests { 108, 101, 32, 83, 116, 114, 117, 99, 116, 0, 0, 0, 0, 1, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 164, 112, 157, 63, 164, 112, 77, 64, 3, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, 97, 110, 116, 32, 118, 97, - 108, 117, 101, 100, 0, 0, 0, 0, 0, 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, + 108, 117, 101, 1, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 101, 0, 0, 0, 0, 0, 0, + 0, ]; let deserializer = UntypedReflectDeserializer::new(®istry); @@ -1392,12 +1501,19 @@ mod tests { map_value: map, struct_value: SomeStruct { foo: 999999999 }, tuple_struct_value: SomeTupleStruct(String::from("Tuple Struct")), + unit_struct: SomeUnitStruct, unit_enum: SomeEnum::Unit, newtype_enum: SomeEnum::NewType(123), tuple_enum: SomeEnum::Tuple(1.23, 3.21), struct_enum: SomeEnum::Struct { foo: String::from("Struct variant value"), }, + ignored_struct: SomeIgnoredStruct { ignored: 0 }, + ignored_tuple_struct: SomeIgnoredTupleStruct(0), + ignored_struct_variant: SomeIgnoredEnum::Struct { + foo: String::default(), + }, + ignored_tuple_variant: SomeIgnoredEnum::Tuple(0.0, 0.0), custom_deserialize: CustomDeserialize { value: 100, inner_struct: SomeDeserializableStruct { foo: 101 }, @@ -1409,14 +1525,15 @@ mod tests { let input = vec![ 129, 217, 40, 98, 101, 118, 121, 95, 114, 101, 102, 108, 101, 99, 116, 58, 58, 115, 101, 114, 100, 101, 58, 58, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, - 83, 116, 114, 117, 99, 116, 158, 123, 172, 72, 101, 108, 108, 111, 32, 119, 111, 114, - 108, 100, 33, 145, 123, 146, 202, 64, 73, 15, 219, 205, 5, 57, 149, 254, 255, 0, 1, 2, - 149, 254, 255, 0, 1, 2, 129, 64, 32, 145, 206, 59, 154, 201, 255, 145, 172, 84, 117, - 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 164, 85, 110, 105, 116, 129, 167, 78, - 101, 119, 84, 121, 112, 101, 123, 129, 165, 84, 117, 112, 108, 101, 146, 202, 63, 157, - 112, 164, 202, 64, 77, 112, 164, 129, 166, 83, 116, 114, 117, 99, 116, 145, 180, 83, - 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, 97, 110, 116, 32, 118, 97, 108, 117, - 101, 146, 100, 145, 101, + 83, 116, 114, 117, 99, 116, 220, 0, 19, 123, 172, 72, 101, 108, 108, 111, 32, 119, 111, + 114, 108, 100, 33, 145, 123, 146, 202, 64, 73, 15, 219, 205, 5, 57, 149, 254, 255, 0, + 1, 2, 149, 254, 255, 0, 1, 2, 129, 64, 32, 145, 206, 59, 154, 201, 255, 145, 172, 84, + 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 144, 164, 85, 110, 105, 116, 129, + 167, 78, 101, 119, 84, 121, 112, 101, 123, 129, 165, 84, 117, 112, 108, 101, 146, 202, + 63, 157, 112, 164, 202, 64, 77, 112, 164, 129, 166, 83, 116, 114, 117, 99, 116, 145, + 180, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, 97, 110, 116, 32, 118, 97, 108, + 117, 101, 144, 144, 129, 166, 83, 116, 114, 117, 99, 116, 144, 129, 165, 84, 117, 112, + 108, 101, 144, 146, 100, 145, 101, ]; let deserializer = UntypedReflectDeserializer::new(®istry); diff --git a/crates/bevy_reflect/src/serde/ser.rs b/crates/bevy_reflect/src/serde/ser.rs index 1dee7dda46bf5..6403b5e99933f 100644 --- a/crates/bevy_reflect/src/serde/ser.rs +++ b/crates/bevy_reflect/src/serde/ser.rs @@ -488,10 +488,15 @@ mod tests { map_value: HashMap, struct_value: SomeStruct, tuple_struct_value: SomeTupleStruct, + unit_struct: SomeUnitStruct, unit_enum: SomeEnum, newtype_enum: SomeEnum, tuple_enum: SomeEnum, struct_enum: SomeEnum, + ignored_struct: SomeIgnoredStruct, + ignored_tuple_struct: SomeIgnoredTupleStruct, + ignored_struct_variant: SomeIgnoredEnum, + ignored_tuple_variant: SomeIgnoredEnum, custom_serialize: CustomSerialize, } @@ -503,6 +508,18 @@ mod tests { #[derive(Reflect, Debug, PartialEq)] struct SomeTupleStruct(String); + #[derive(Reflect, FromReflect, Debug, PartialEq)] + struct SomeUnitStruct; + + #[derive(Reflect, FromReflect, Debug, PartialEq)] + struct SomeIgnoredStruct { + #[reflect(ignore)] + ignored: i32, + } + + #[derive(Reflect, FromReflect, Debug, PartialEq)] + struct SomeIgnoredTupleStruct(#[reflect(ignore)] i32); + #[derive(Reflect, Debug, PartialEq)] enum SomeEnum { Unit, @@ -511,6 +528,15 @@ mod tests { Struct { foo: String }, } + #[derive(Reflect, FromReflect, Debug, PartialEq)] + enum SomeIgnoredEnum { + Tuple(#[reflect(ignore)] f32, #[reflect(ignore)] f32), + Struct { + #[reflect(ignore)] + foo: String, + }, + } + #[derive(Reflect, Debug, PartialEq, Serialize)] struct SomeSerializableStruct { foo: i64, @@ -532,6 +558,10 @@ mod tests { registry.register::(); registry.register::(); registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); registry.register::(); registry.register::(); registry.register::(); @@ -557,12 +587,19 @@ mod tests { map_value: map, struct_value: SomeStruct { foo: 999999999 }, tuple_struct_value: SomeTupleStruct(String::from("Tuple Struct")), + unit_struct: SomeUnitStruct, unit_enum: SomeEnum::Unit, newtype_enum: SomeEnum::NewType(123), tuple_enum: SomeEnum::Tuple(1.23, 3.21), struct_enum: SomeEnum::Struct { foo: String::from("Struct variant value"), }, + ignored_struct: SomeIgnoredStruct { ignored: 123 }, + ignored_tuple_struct: SomeIgnoredTupleStruct(123), + ignored_struct_variant: SomeIgnoredEnum::Struct { + foo: String::from("Struct Variant"), + }, + ignored_tuple_variant: SomeIgnoredEnum::Tuple(1.23, 3.45), custom_serialize: CustomSerialize { value: 100, inner_struct: SomeSerializableStruct { foo: 101 }, @@ -600,12 +637,17 @@ mod tests { foo: 999999999, ), tuple_struct_value: ("Tuple Struct"), + unit_struct: (), unit_enum: Unit, newtype_enum: NewType(123), tuple_enum: Tuple(1.23, 3.21), struct_enum: Struct( foo: "Struct variant value", ), + ignored_struct: (), + ignored_tuple_struct: (), + ignored_struct_variant: Struct(), + ignored_tuple_variant: Tuple(), custom_serialize: ( value: 100, renamed: ( @@ -745,12 +787,19 @@ mod tests { map_value: map, struct_value: SomeStruct { foo: 999999999 }, tuple_struct_value: SomeTupleStruct(String::from("Tuple Struct")), + unit_struct: SomeUnitStruct, unit_enum: SomeEnum::Unit, newtype_enum: SomeEnum::NewType(123), tuple_enum: SomeEnum::Tuple(1.23, 3.21), struct_enum: SomeEnum::Struct { foo: String::from("Struct variant value"), }, + ignored_struct: SomeIgnoredStruct { ignored: 123 }, + ignored_tuple_struct: SomeIgnoredTupleStruct(123), + ignored_struct_variant: SomeIgnoredEnum::Struct { + foo: String::from("Struct Variant"), + }, + ignored_tuple_variant: SomeIgnoredEnum::Tuple(1.23, 3.45), custom_serialize: CustomSerialize { value: 100, inner_struct: SomeSerializableStruct { foo: 101 }, @@ -774,7 +823,8 @@ mod tests { 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 0, 0, 0, 0, 1, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 164, 112, 157, 63, 164, 112, 77, 64, 3, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, 97, 110, 116, 32, 118, 97, - 108, 117, 101, 100, 0, 0, 0, 0, 0, 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, + 108, 117, 101, 1, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 101, 0, 0, 0, 0, 0, 0, + 0, ]; assert_eq!(expected, bytes); @@ -795,12 +845,19 @@ mod tests { map_value: map, struct_value: SomeStruct { foo: 999999999 }, tuple_struct_value: SomeTupleStruct(String::from("Tuple Struct")), + unit_struct: SomeUnitStruct, unit_enum: SomeEnum::Unit, newtype_enum: SomeEnum::NewType(123), tuple_enum: SomeEnum::Tuple(1.23, 3.21), struct_enum: SomeEnum::Struct { foo: String::from("Struct variant value"), }, + ignored_struct: SomeIgnoredStruct { ignored: 123 }, + ignored_tuple_struct: SomeIgnoredTupleStruct(123), + ignored_struct_variant: SomeIgnoredEnum::Struct { + foo: String::from("Struct Variant"), + }, + ignored_tuple_variant: SomeIgnoredEnum::Tuple(1.23, 3.45), custom_serialize: CustomSerialize { value: 100, inner_struct: SomeSerializableStruct { foo: 101 }, @@ -815,14 +872,15 @@ mod tests { let expected: Vec = vec![ 129, 217, 41, 98, 101, 118, 121, 95, 114, 101, 102, 108, 101, 99, 116, 58, 58, 115, 101, 114, 100, 101, 58, 58, 115, 101, 114, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, - 121, 83, 116, 114, 117, 99, 116, 158, 123, 172, 72, 101, 108, 108, 111, 32, 119, 111, - 114, 108, 100, 33, 145, 123, 146, 202, 64, 73, 15, 219, 205, 5, 57, 149, 254, 255, 0, - 1, 2, 149, 254, 255, 0, 1, 2, 129, 64, 32, 145, 206, 59, 154, 201, 255, 145, 172, 84, - 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 164, 85, 110, 105, 116, 129, 167, - 78, 101, 119, 84, 121, 112, 101, 123, 129, 165, 84, 117, 112, 108, 101, 146, 202, 63, - 157, 112, 164, 202, 64, 77, 112, 164, 129, 166, 83, 116, 114, 117, 99, 116, 145, 180, - 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, 97, 110, 116, 32, 118, 97, 108, 117, - 101, 146, 100, 145, 101, + 121, 83, 116, 114, 117, 99, 116, 220, 0, 19, 123, 172, 72, 101, 108, 108, 111, 32, 119, + 111, 114, 108, 100, 33, 145, 123, 146, 202, 64, 73, 15, 219, 205, 5, 57, 149, 254, 255, + 0, 1, 2, 149, 254, 255, 0, 1, 2, 129, 64, 32, 145, 206, 59, 154, 201, 255, 145, 172, + 84, 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 144, 164, 85, 110, 105, 116, + 129, 167, 78, 101, 119, 84, 121, 112, 101, 123, 129, 165, 84, 117, 112, 108, 101, 146, + 202, 63, 157, 112, 164, 202, 64, 77, 112, 164, 129, 166, 83, 116, 114, 117, 99, 116, + 145, 180, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, 97, 110, 116, 32, 118, 97, + 108, 117, 101, 144, 144, 129, 166, 83, 116, 114, 117, 99, 116, 144, 129, 165, 84, 117, + 112, 108, 101, 144, 146, 100, 145, 101, ]; assert_eq!(expected, bytes); 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 0000000000000..5af57acd621f8 --- /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 0000000000000..52faa4f1d6034 --- /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 0000000000000..d0d1683dd6b97 --- /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 0000000000000..dbab6a4ef08f6 --- /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 0000000000000..4a97e5d8278a2 --- /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 0000000000000..156fb6cda17d7 --- /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 0000000000000..60d32b81f38ec --- /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 4c8a2b14b2ac9..bbc3c3850cf0b 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 7d986c9a3125a..72bcfa3556df0 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 224e61e787a92..f57929f30caeb 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/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index daf1f6cb56561..df81b87b261c8 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -7,12 +7,7 @@ pub use camera::*; pub use camera_driver_node::*; pub use projection::*; -use crate::{ - primitives::Aabb, - render_graph::RenderGraph, - view::{ComputedVisibility, RenderLayers, Visibility, VisibleEntities}, - RenderApp, RenderStage, -}; +use crate::{render_graph::RenderGraph, RenderApp, RenderStage}; use bevy_app::{App, Plugin}; #[derive(Default)] @@ -23,14 +18,10 @@ impl Plugin for CameraPlugin { app.register_type::() .register_type::() .register_type::>() - .register_type::() - .register_type::() - .register_type::() .register_type::() .register_type::() - .register_type::() .register_type::() - .register_type::() + .register_type::() .add_plugin(CameraProjectionPlugin::::default()) .add_plugin(CameraProjectionPlugin::::default()) .add_plugin(CameraProjectionPlugin::::default()); diff --git a/crates/bevy_render/src/color/mod.rs b/crates/bevy_render/src/color/mod.rs index 0362d8386a9e4..4fe72e7c3b348 100644 --- a/crates/bevy_render/src/color/mod.rs +++ b/crates/bevy_render/src/color/mod.rs @@ -358,6 +358,13 @@ impl Color { self } + /// Returns this color with red set to a new value in sRGB colorspace. + #[must_use] + pub fn with_r(mut self, r: f32) -> Self { + self.set_r(r); + self + } + /// Set green in sRGB colorspace. pub fn set_g(&mut self, g: f32) -> &mut Self { *self = self.as_rgba(); @@ -368,6 +375,13 @@ impl Color { self } + /// Returns this color with green set to a new value in sRGB colorspace. + #[must_use] + pub fn with_g(mut self, g: f32) -> Self { + self.set_g(g); + self + } + /// Set blue in sRGB colorspace. pub fn set_b(&mut self, b: f32) -> &mut Self { *self = self.as_rgba(); @@ -378,6 +392,13 @@ impl Color { self } + /// Returns this color with blue set to a new value in sRGB colorspace. + #[must_use] + pub fn with_b(mut self, b: f32) -> Self { + self.set_b(b); + self + } + /// Get alpha. #[inline(always)] pub fn a(&self) -> f32 { @@ -400,6 +421,13 @@ impl Color { self } + /// Returns this color with a new alpha value. + #[must_use] + pub fn with_a(mut self, a: f32) -> Self { + self.set_a(a); + self + } + /// Converts a `Color` to variant `Color::Rgba` pub fn as_rgba(self: &Color) -> Color { match self { diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index 573c3e0cff23f..7cae7d8065ed5 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -37,8 +37,26 @@ pub trait ExtractComponent: Component { type Query: WorldQuery + ReadOnlyWorldQuery; /// Filters the entities with additional constraints. type Filter: WorldQuery + ReadOnlyWorldQuery; + + /// The output from extraction. + /// + /// Returning `None` based on the queried item can allow early optimization, + /// for example if there is an `enabled: bool` field on `Self`, or by only accepting + /// values within certain thresholds. + /// + /// The output may be different from the queried component. + /// This can be useful for example if only a subset of the fields are useful + /// in the render world. + /// + /// `Out` has a [`Bundle`] trait bound instead of a [`Component`] trait bound in order to allow use cases + /// such as tuples of components as output. + type Out: Bundle; + + // TODO: https://github.com/rust-lang/rust/issues/29661 + // type Out: Component = Self; + /// Defines how the component is transferred into the "render world". - fn extract_component(item: QueryItem<'_, Self::Query>) -> Self; + fn extract_component(item: QueryItem<'_, Self::Query>) -> Option; } /// This plugin prepares the components of the corresponding type for the GPU @@ -172,10 +190,11 @@ impl Plugin for ExtractComponentPlugin { impl ExtractComponent for Handle { type Query = Read>; type Filter = (); + type Out = Handle; #[inline] - fn extract_component(handle: QueryItem<'_, Self::Query>) -> Self { - handle.clone_weak() + fn extract_component(handle: QueryItem<'_, Self::Query>) -> Option { + Some(handle.clone_weak()) } } @@ -187,7 +206,9 @@ fn extract_components( ) { let mut values = Vec::with_capacity(*previous_len); for (entity, query_item) in &query { - values.push((entity, C::extract_component(query_item))); + if let Some(component) = C::extract_component(query_item) { + values.push((entity, component)); + } } *previous_len = values.len(); commands.insert_or_spawn_batch(values); @@ -202,7 +223,9 @@ fn extract_visible_components( let mut values = Vec::with_capacity(*previous_len); for (entity, computed_visibility, query_item) in &query { if computed_visibility.is_visible() { - values.push((entity, C::extract_component(query_item))); + if let Some(component) = C::extract_component(query_item) { + values.push((entity, component)); + } } } *previous_len = values.len(); diff --git a/crates/bevy_render/src/extract_param.rs b/crates/bevy_render/src/extract_param.rs index cdfb462c2324b..b3d4ba665a55a 100644 --- a/crates/bevy_render/src/extract_param.rs +++ b/crates/bevy_render/src/extract_param.rs @@ -2,8 +2,8 @@ use crate::MainWorld; use bevy_ecs::{ prelude::*, system::{ - ReadOnlySystemParamFetch, ResState, SystemMeta, SystemParam, SystemParamFetch, - SystemParamItem, SystemParamState, SystemState, + ReadOnlySystemParam, ResState, SystemMeta, SystemParam, SystemParamItem, SystemParamState, + SystemState, }, }; use std::ops::{Deref, DerefMut}; @@ -42,18 +42,18 @@ use std::ops::{Deref, DerefMut}; /// /// [`RenderStage::Extract`]: crate::RenderStage::Extract /// [Window]: bevy_window::Window -pub struct Extract<'w, 's, P: SystemParam + 'static> +pub struct Extract<'w, 's, P> where - P::Fetch: ReadOnlySystemParamFetch, + P: ReadOnlySystemParam + 'static, { - item: >::Item, + item: SystemParamItem<'w, 's, P>, } -impl<'w, 's, P: SystemParam> SystemParam for Extract<'w, 's, P> +impl<'w, 's, P> SystemParam for Extract<'w, 's, P> where - P::Fetch: ReadOnlySystemParamFetch, + P: ReadOnlySystemParam, { - type Fetch = ExtractState

; + type State = ExtractState

; } #[doc(hidden)] @@ -64,7 +64,12 @@ pub struct ExtractState { // SAFETY: only accesses MainWorld resource with read only system params using ResState, // which is initialized in init() -unsafe impl SystemParamState for ExtractState

{ +unsafe impl SystemParamState for ExtractState

+where + P: ReadOnlySystemParam + 'static, +{ + type Item<'w, 's> = Extract<'w, 's, P>; + fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { let mut main_world = world.resource_mut::(); Self { @@ -72,20 +77,13 @@ unsafe impl SystemParamState for ExtractState

{ main_world_state: ResState::init(world, system_meta), } } -} - -impl<'w, 's, P: SystemParam + 'static> SystemParamFetch<'w, 's> for ExtractState

-where - P::Fetch: ReadOnlySystemParamFetch, -{ - type Item = Extract<'w, 's, P>; - unsafe fn get_param( + unsafe fn get_param<'w, 's>( state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, change_tick: u32, - ) -> Self::Item { + ) -> Self::Item<'w, 's> { let main_world = ResState::::get_param( &mut state.main_world_state, system_meta, @@ -99,9 +97,9 @@ where impl<'w, 's, P: SystemParam> Deref for Extract<'w, 's, P> where - P::Fetch: ReadOnlySystemParamFetch, + P: ReadOnlySystemParam, { - type Target = >::Item; + type Target = SystemParamItem<'w, 's, P>; #[inline] fn deref(&self) -> &Self::Target { @@ -111,7 +109,7 @@ where impl<'w, 's, P: SystemParam> DerefMut for Extract<'w, 's, P> where - P::Fetch: ReadOnlySystemParamFetch, + P: ReadOnlySystemParam, { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { @@ -121,7 +119,7 @@ where impl<'a, 'w, 's, P: SystemParam> IntoIterator for &'a Extract<'w, 's, P> where - P::Fetch: ReadOnlySystemParamFetch, + P: ReadOnlySystemParam, &'a SystemParamItem<'w, 's, P>: IntoIterator, { type Item = <&'a SystemParamItem<'w, 's, P> as IntoIterator>::Item; diff --git a/crates/bevy_render/src/globals.rs b/crates/bevy_render/src/globals.rs index eea40342e1046..a0b160dd4b27d 100644 --- a/crates/bevy_render/src/globals.rs +++ b/crates/bevy_render/src/globals.rs @@ -14,16 +14,22 @@ pub struct GlobalsPlugin; impl Plugin for GlobalsPlugin { fn build(&self, app: &mut App) { + app.register_type::(); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() .init_resource::

{ /// Adds the [`Draw`] function and associates 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`. pub fn get_id(&self) -> Option { self.indices.get(&TypeId::of::()).copied() } + + /// Retrieves the id of the [`Draw`] function corresponding to their associated type `T`. + /// + /// Fallible wrapper for [`Self::get_id()`] + /// + /// ## Panics + /// If the id doesn't exist it will panic + pub fn id(&self) -> DrawFunctionId { + self.get_id::().unwrap_or_else(|| { + panic!( + "Draw function {} not found for {}", + std::any::type_name::(), + std::any::type_name::

() + ) + }) + } } /// Stores all draw functions for the [`PhaseItem`] type hidden behind a reader-writer lock. @@ -309,7 +325,7 @@ impl> RenderCommandState { impl + Send + Sync + 'static> Draw

for RenderCommandState where - ::Fetch: ReadOnlySystemParamFetch, + C::Param: ReadOnlySystemParam, { /// Prepares the ECS parameters for the wrapped [`RenderCommand`] and then renders it. fn draw<'w>( @@ -332,7 +348,7 @@ pub trait AddRenderCommand { &mut self, ) -> &mut Self where - ::Fetch: ReadOnlySystemParamFetch; + C::Param: ReadOnlySystemParam; } impl AddRenderCommand for App { @@ -340,7 +356,7 @@ impl AddRenderCommand for App { &mut self, ) -> &mut Self where - ::Fetch: ReadOnlySystemParamFetch, + C::Param: ReadOnlySystemParam, { let draw_function = RenderCommandState::::new(&mut self.world); let draw_functions = self diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 29564481ca454..2aa684ece7867 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -1,10 +1,12 @@ mod draw; mod draw_state; +use bevy_ecs::entity::Entity; pub use draw::*; pub use draw_state::*; use bevy_ecs::prelude::{Component, Query}; +use bevy_ecs::world::World; /// A resource to collect and sort draw requests for specific [`PhaseItems`](PhaseItem). #[derive(Component)] @@ -29,6 +31,21 @@ impl RenderPhase { pub fn sort(&mut self) { I::sort(&mut self.items); } + + 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(); + + 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 { diff --git a/crates/bevy_render/src/render_resource/bind_group.rs b/crates/bevy_render/src/render_resource/bind_group.rs index 8362a5c899ad5..5bc13b1d1f47a 100644 --- a/crates/bevy_render/src/render_resource/bind_group.rs +++ b/crates/bevy_render/src/render_resource/bind_group.rs @@ -1,20 +1,18 @@ -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; -use std::{ops::Deref, sync::Arc}; +pub use bevy_render_macros::AsBindGroup; +use encase::ShaderType; +use std::ops::Deref; use wgpu::BindingResource; -/// A [`BindGroup`] identifier. -#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] -pub struct BindGroupId(Uuid); +define_atomic_id!(BindGroupId); +render_resource_wrapper!(ErasedBindGroup, wgpu::BindGroup); /// Bind groups are responsible for binding render resources (e.g. buffers, textures, samplers) /// to a [`TrackedRenderPass`](crate::render_phase::TrackedRenderPass). @@ -25,7 +23,7 @@ pub struct BindGroupId(Uuid); #[derive(Clone, Debug)] pub struct BindGroup { id: BindGroupId, - value: Arc, + value: ErasedBindGroup, } impl BindGroup { @@ -39,8 +37,8 @@ impl BindGroup { impl From for BindGroup { fn from(value: wgpu::BindGroup) -> Self { BindGroup { - id: BindGroupId(Uuid::new_v4()), - value: Arc::new(value), + id: BindGroupId::new(), + value: ErasedBindGroup::new(value), } } } @@ -90,12 +88,8 @@ impl Deref for BindGroup { /// In WGSL shaders, the binding would look like this: /// /// ```wgsl -/// struct CoolMaterial { -/// color: vec4, -/// }; -/// /// @group(1) @binding(0) -/// var material: CoolMaterial; +/// var color: vec4; /// @group(1) @binding(1) /// var color_texture: texture_2d; /// @group(1) @binding(2) @@ -105,23 +99,39 @@ impl Deref for BindGroup { /// are generally bound to group 1. /// /// The following field-level attributes are supported: +/// /// * `uniform(BINDING_INDEX)` /// * The field will be converted to a shader-compatible type using the [`ShaderType`] trait, written to a [`Buffer`], and bound as a uniform. /// [`ShaderType`] is implemented for most math types already, such as [`f32`], [`Vec4`](bevy_math::Vec4), and /// [`Color`](crate::color::Color). It can also be derived for custom structs. -/// * `texture(BINDING_INDEX)` +/// +/// * `texture(BINDING_INDEX, arguments)` /// * This field's [`Handle`](bevy_asset::Handle) will be used to look up the matching [`Texture`](crate::render_resource::Texture) /// GPU resource, which will be bound as a texture in shaders. The field will be assumed to implement [`Into>>`]. In practice, /// most fields should be a [`Handle`](bevy_asset::Handle) or [`Option>`]. If the value of an [`Option>`] is /// [`None`], the [`FallbackImage`] resource will be used instead. This attribute can be used in conjunction with a `sampler` binding attribute /// (with a different binding index) if a binding of the sampler for the [`Image`] is also required. -/// * `sampler(BINDING_INDEX)` +/// +/// | Arguments | Values | Default | +/// |-----------------------|-------------------------------------------------------------------------|----------------------| +/// | `dimension` = "..." | `"1d"`, `"2d"`, `"2d_array"`, `"3d"`, `"cube"`, `"cube_array"` | `"2d"` | +/// | `sample_type` = "..." | `"float"`, `"depth"`, `"s_int"` or `"u_int"` | `"float"` | +/// | `filterable` = ... | `true`, `false` | `true` | +/// | `multisampled` = ... | `true`, `false` | `false` | +/// | `visibility(...)` | `all`, `none`, or a list-combination of `vertex`, `fragment`, `compute` | `vertex`, `fragment` | +/// +/// * `sampler(BINDING_INDEX, arguments)` /// * This field's [`Handle`](bevy_asset::Handle) will be used to look up the matching [`Sampler`](crate::render_resource::Sampler) GPU /// resource, which will be bound as a sampler in shaders. The field will be assumed to implement [`Into>>`]. In practice, /// most fields should be a [`Handle`](bevy_asset::Handle) or [`Option>`]. If the value of an [`Option>`] is /// [`None`], the [`FallbackImage`] resource will be used instead. This attribute can be used in conjunction with a `texture` binding attribute /// (with a different binding index) if a binding of the texture for the [`Image`] is also required. /// +/// | Arguments | Values | Default | +/// |------------------------|-------------------------------------------------------------------------|------------------------| +/// | `sampler_type` = "..." | `"filtering"`, `"non_filtering"`, `"comparison"`. | `"filtering"` | +/// | `visibility(...)` | `all`, `none`, or a list-combination of `vertex`, `fragment`, `compute` | `vertex`, `fragment` | +/// /// Note that fields without field-level binding attributes will be ignored. /// ``` /// # use bevy_render::{color::Color, render_resource::AsBindGroup}; @@ -238,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; @@ -249,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. @@ -262,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 de0ce253f8a5f..9793f1391d188 100644 --- a/crates/bevy_render/src/render_resource/bind_group_layout.rs +++ b/crates/bevy_render/src/render_resource/bind_group_layout.rs @@ -1,13 +1,13 @@ -use bevy_reflect::Uuid; -use std::{ops::Deref, sync::Arc}; +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)] pub struct BindGroupLayout { id: BindGroupLayoutId, - value: Arc, + value: ErasedBindGroupLayout, } impl PartialEq for BindGroupLayout { @@ -31,8 +31,8 @@ impl BindGroupLayout { impl From for BindGroupLayout { fn from(value: wgpu::BindGroupLayout) -> Self { BindGroupLayout { - id: BindGroupLayoutId(Uuid::new_v4()), - value: Arc::new(value), + 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 084a3290c26f1..9867945673efb 100644 --- a/crates/bevy_render/src/render_resource/buffer.rs +++ b/crates/bevy_render/src/render_resource/buffer.rs @@ -1,16 +1,13 @@ -use bevy_utils::Uuid; -use std::{ - ops::{Bound, Deref, RangeBounds}, - sync::Arc, -}; +use crate::{define_atomic_id, render_resource::resource_macros::render_resource_wrapper}; +use std::ops::{Bound, Deref, RangeBounds}; -#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] -pub struct BufferId(Uuid); +define_atomic_id!(BufferId); +render_resource_wrapper!(ErasedBuffer, wgpu::Buffer); #[derive(Clone, Debug)] pub struct Buffer { id: BufferId, - value: Arc, + value: ErasedBuffer, } impl Buffer { @@ -41,8 +38,8 @@ impl Buffer { impl From for Buffer { fn from(value: wgpu::Buffer) -> Self { Buffer { - id: BufferId(Uuid::new_v4()), - value: Arc::new(value), + 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 1d3086796dbc5..4a3c744e071c9 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/mod.rs b/crates/bevy_render/src/render_resource/mod.rs index a0206b1c7f233..33a010ff502d1 100644 --- a/crates/bevy_render/src/render_resource/mod.rs +++ b/crates/bevy_render/src/render_resource/mod.rs @@ -5,6 +5,7 @@ mod buffer_vec; mod pipeline; mod pipeline_cache; mod pipeline_specializer; +pub mod resource_macros; mod shader; mod storage_buffer; mod texture; diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index 7cf6ded22c2d4..edd3d8f6d67cb 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -1,15 +1,17 @@ -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, sync::Arc}; +use std::{borrow::Cow, ops::Deref}; use wgpu::{ BufferAddress, ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState, VertexAttribute, VertexFormat, VertexStepMode, }; -/// 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. /// @@ -18,7 +20,7 @@ pub struct RenderPipelineId(Uuid); #[derive(Clone, Debug)] pub struct RenderPipeline { id: RenderPipelineId, - value: Arc, + value: ErasedRenderPipeline, } impl RenderPipeline { @@ -31,8 +33,8 @@ impl RenderPipeline { impl From for RenderPipeline { fn from(value: wgpu::RenderPipeline) -> Self { RenderPipeline { - id: RenderPipelineId(Uuid::new_v4()), - value: Arc::new(value), + id: RenderPipelineId::new(), + value: ErasedRenderPipeline::new(value), } } } @@ -46,9 +48,8 @@ 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. /// @@ -57,7 +58,7 @@ pub struct ComputePipelineId(Uuid); #[derive(Clone, Debug)] pub struct ComputePipeline { id: ComputePipelineId, - value: Arc, + value: ErasedComputePipeline, } impl ComputePipeline { @@ -71,8 +72,8 @@ impl ComputePipeline { impl From for ComputePipeline { fn from(value: wgpu::ComputePipeline) -> Self { ComputePipeline { - id: ComputePipelineId(Uuid::new_v4()), - value: Arc::new(value), + id: ComputePipelineId::new(), + value: ErasedComputePipeline::new(value), } } } @@ -109,7 +110,7 @@ pub struct RenderPipelineDescriptor { pub struct VertexState { /// The compiled shader module for this stage. pub shader: Handle, - pub shader_defs: Vec, + pub shader_defs: Vec, /// The name of the entry point in the compiled shader. There must be a /// function with this name in the shader. pub entry_point: Cow<'static, str>, @@ -161,7 +162,7 @@ impl VertexBufferLayout { pub struct FragmentState { /// The compiled shader module for this stage. pub shader: Handle, - pub shader_defs: Vec, + pub shader_defs: Vec, /// The name of the entry point in the compiled shader. There must be a /// function with this name in the shader. pub entry_point: Cow<'static, str>, @@ -176,7 +177,7 @@ pub struct ComputePipelineDescriptor { pub layout: Option>, /// The compiled shader module for this stage. pub shader: Handle, - pub shader_defs: Vec, + pub shader_defs: Vec, /// The name of the entry point in the compiled shader. There must be a /// function with this name in the shader. pub entry_point: Cow<'static, str>, diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index e03857e788a8e..0209c2e8c9d97 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -17,12 +17,14 @@ use bevy_utils::{ tracing::{debug, error}, Entry, HashMap, HashSet, }; -use std::{hash::Hash, iter::FusedIterator, mem, ops::Deref, sync::Arc}; +use std::{hash::Hash, iter::FusedIterator, mem, ops::Deref}; use thiserror::Error; -use wgpu::{ - BufferBindingType, PipelineLayoutDescriptor, ShaderModule, - VertexBufferLayout as RawVertexBufferLayout, -}; +use wgpu::{PipelineLayoutDescriptor, VertexBufferLayout as RawVertexBufferLayout}; + +use crate::render_resource::resource_macros::*; + +render_resource_wrapper!(ErasedShaderModule, wgpu::ShaderModule); +render_resource_wrapper!(ErasedPipelineLayout, wgpu::PipelineLayout); /// A descriptor for a [`Pipeline`]. /// @@ -103,7 +105,7 @@ impl CachedPipelineState { #[derive(Default)] struct ShaderData { pipelines: HashSet, - processed_shaders: HashMap, Arc>, + processed_shaders: HashMap, ErasedShaderModule>, resolved_imports: HashMap>, dependents: HashSet>, } @@ -117,14 +119,43 @@ struct ShaderCache { processor: ShaderProcessor, } +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub enum ShaderDefVal { + Bool(String, bool), + Int(String, i32), + UInt(String, u32), +} + +impl From<&str> for ShaderDefVal { + fn from(key: &str) -> Self { + ShaderDefVal::Bool(key.to_string(), true) + } +} + +impl From for ShaderDefVal { + fn from(key: String) -> Self { + ShaderDefVal::Bool(key, true) + } +} + +impl ShaderDefVal { + pub fn value_as_string(&self) -> String { + match self { + ShaderDefVal::Bool(_, def) => def.to_string(), + ShaderDefVal::Int(_, def) => def.to_string(), + ShaderDefVal::UInt(_, def) => def.to_string(), + } + } +} + impl ShaderCache { fn get( &mut self, render_device: &RenderDevice, pipeline: CachedPipelineId, handle: &Handle, - shader_defs: &[String], - ) -> Result, PipelineCacheError> { + shader_defs: &[ShaderDefVal], + ) -> Result { let shader = self .shaders .get(handle) @@ -152,22 +183,14 @@ impl ShaderCache { let mut shader_defs = shader_defs.to_vec(); #[cfg(feature = "webgl")] { - shader_defs.push(String::from("NO_ARRAY_TEXTURES_SUPPORT")); - shader_defs.push(String::from("SIXTEEN_BYTE_ALIGNMENT")); + shader_defs.push("NO_ARRAY_TEXTURES_SUPPORT".into()); + shader_defs.push("SIXTEEN_BYTE_ALIGNMENT".into()); } - // TODO: 3 is the value from CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT declared in bevy_pbr - // consider exposing this in shaders in a more generally useful way, such as: - // # if AVAILABLE_STORAGE_BUFFER_BINDINGS == 3 - // /* use storage buffers here */ - // # elif - // /* use uniforms here */ - if !matches!( - render_device.get_supported_read_only_binding_type(3), - BufferBindingType::Storage { .. } - ) { - shader_defs.push(String::from("NO_STORAGE_BUFFERS_SUPPORT")); - } + shader_defs.push(ShaderDefVal::UInt( + String::from("AVAILABLE_STORAGE_BUFFER_BINDINGS"), + render_device.limits().max_storage_buffers_per_shader_stage, + )); debug!( "processing shader {:?}, with shader defs {:?}", @@ -204,7 +227,7 @@ impl ShaderCache { return Err(PipelineCacheError::CreateShaderModule(description)); } - entry.insert(Arc::new(shader_module)) + entry.insert(ErasedShaderModule::new(shader_module)) } }; @@ -276,7 +299,7 @@ impl ShaderCache { #[derive(Default)] struct LayoutCache { - layouts: HashMap, wgpu::PipelineLayout>, + layouts: HashMap, ErasedPipelineLayout>, } impl LayoutCache { @@ -291,10 +314,12 @@ impl LayoutCache { .iter() .map(|l| l.value()) .collect::>(); - render_device.create_pipeline_layout(&PipelineLayoutDescriptor { - bind_group_layouts: &bind_group_layouts, - ..default() - }) + ErasedPipelineLayout::new(render_device.create_pipeline_layout( + &PipelineLayoutDescriptor { + bind_group_layouts: &bind_group_layouts, + ..default() + }, + )) }) } } diff --git a/crates/bevy_render/src/render_resource/resource_macros.rs b/crates/bevy_render/src/render_resource/resource_macros.rs new file mode 100644 index 0000000000000..e27380426a169 --- /dev/null +++ b/crates/bevy_render/src/render_resource/resource_macros.rs @@ -0,0 +1,151 @@ +// structs containing wgpu types take a long time to compile. this is particularly bad for generic +// structs containing wgpu structs. we avoid that in debug builds (and for cargo check and rust analyzer) +// by boxing and type-erasing with the `render_resource_wrapper` macro. +// analysis from https://github.com/bevyengine/bevy/pull/5950#issuecomment-1243473071 indicates this is +// due to `evaluate_obligations`. we should check if this can be removed after a fix lands for +// https://github.com/rust-lang/rust/issues/99188 (and after other `evaluate_obligations`-related changes). +#[cfg(debug_assertions)] +#[macro_export] +macro_rules! render_resource_wrapper { + ($wrapper_type:ident, $wgpu_type:ty) => { + #[derive(Clone, Debug)] + pub struct $wrapper_type(Option>>); + + impl $wrapper_type { + pub fn new(value: $wgpu_type) -> Self { + unsafe { + Self(Some(std::sync::Arc::new(std::mem::transmute(Box::new( + value, + ))))) + } + } + + pub fn try_unwrap(mut self) -> Option<$wgpu_type> { + let inner = self.0.take(); + if let Some(inner) = inner { + match std::sync::Arc::try_unwrap(inner) { + Ok(untyped_box) => { + let typed_box = unsafe { + std::mem::transmute::, Box<$wgpu_type>>(untyped_box) + }; + Some(*typed_box) + } + Err(inner) => { + let _ = unsafe { + std::mem::transmute::< + std::sync::Arc>, + std::sync::Arc>, + >(inner) + }; + None + } + } + } else { + None + } + } + } + + impl std::ops::Deref for $wrapper_type { + type Target = $wgpu_type; + + fn deref(&self) -> &Self::Target { + let untyped_box = self + .0 + .as_ref() + .expect("render_resource_wrapper inner value has already been taken (via drop or try_unwrap") + .as_ref(); + + let typed_box = + unsafe { std::mem::transmute::<&Box<()>, &Box<$wgpu_type>>(untyped_box) }; + typed_box.as_ref() + } + } + + impl Drop for $wrapper_type { + fn drop(&mut self) { + let inner = self.0.take(); + if let Some(inner) = inner { + let _ = unsafe { + std::mem::transmute::< + std::sync::Arc>, + std::sync::Arc>, + >(inner) + }; + } + } + } + + // Arc> and Arc<()> will be Sync and Send even when $wgpu_type is not Sync or Send. + // We ensure correctness by checking that $wgpu_type does implement Send and Sync. + // If in future there is a case where a wrapper is required for a non-send/sync type + // we can implement a macro variant that also does `impl !Send for $wrapper_type {}` and + // `impl !Sync for $wrapper_type {}` + const _: () = { + trait AssertSendSyncBound: Send + Sync {} + impl AssertSendSyncBound for $wgpu_type {} + }; + }; +} + +#[cfg(not(debug_assertions))] +#[macro_export] +macro_rules! render_resource_wrapper { + ($wrapper_type:ident, $wgpu_type:ty) => { + #[derive(Clone, Debug)] + pub struct $wrapper_type(std::sync::Arc<$wgpu_type>); + + impl $wrapper_type { + pub fn new(value: $wgpu_type) -> Self { + Self(std::sync::Arc::new(value)) + } + + pub fn try_unwrap(self) -> Option<$wgpu_type> { + std::sync::Arc::try_unwrap(self.0).ok() + } + } + + impl std::ops::Deref for $wrapper_type { + type Target = $wgpu_type; + + fn deref(&self) -> &Self::Target { + self.0.as_ref() + } + } + + const _: () = { + trait AssertSendSyncBound: Send + Sync {} + impl AssertSendSyncBound for $wgpu_type {} + }; + }; +} + +#[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 b216c445588e2..08f01f64fa81c 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, collections::HashSet, marker::Copy, ops::Deref, path::PathBuf, str::FromStr, -}; +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}; - -#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] -pub struct ShaderId(Uuid); +use wgpu::{util::make_spirv, Features, ShaderModuleDescriptor, ShaderSource}; -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 { @@ -297,12 +286,24 @@ pub enum ProcessShaderError { NotEnoughEndIfs, #[error("This Shader's format does not support processing shader defs.")] ShaderFormatDoesNotSupportShaderDefs, - #[error("This Shader's formatdoes not support imports.")] + #[error("This Shader's format does not support imports.")] ShaderFormatDoesNotSupportImports, #[error("Unresolved import: {0:?}.")] UnresolvedImport(ShaderImport), #[error("The shader import {0:?} does not match the source file type. Support for this might be added in the future.")] MismatchedImportFormat(ShaderImport), + #[error("Unknown shader def operator: '{operator}'")] + UnknownShaderDefOperator { operator: String }, + #[error("Unknown shader def: '{shader_def_name}'")] + UnknownShaderDef { shader_def_name: String }, + #[error( + "Invalid shader def comparison for '{shader_def_name}': expected {expected}, got {value}" + )] + InvalidShaderDefComparisonValue { + shader_def_name: String, + expected: String, + value: String, + }, } pub struct ShaderImportProcessor { @@ -371,8 +372,11 @@ pub static SHADER_IMPORT_PROCESSOR: Lazy = pub struct ShaderProcessor { ifdef_regex: Regex, ifndef_regex: Regex, + ifop_regex: Regex, else_regex: Regex, endif_regex: Regex, + def_regex: Regex, + def_regex_delimited: Regex, } impl Default for ShaderProcessor { @@ -380,8 +384,11 @@ impl Default for ShaderProcessor { Self { ifdef_regex: Regex::new(r"^\s*#\s*ifdef\s*([\w|\d|_]+)").unwrap(), ifndef_regex: Regex::new(r"^\s*#\s*ifndef\s*([\w|\d|_]+)").unwrap(), + ifop_regex: Regex::new(r"^\s*#\s*if\s*([\w|\d|_]+)\s*([^\s]*)\s*([\w|\d]+)").unwrap(), else_regex: Regex::new(r"^\s*#\s*else").unwrap(), endif_regex: Regex::new(r"^\s*#\s*endif").unwrap(), + def_regex: Regex::new(r"#\s*([\w|\d|_]+)").unwrap(), + def_regex_delimited: Regex::new(r"#\s*\{([\w|\d|_]+)\}").unwrap(), } } } @@ -390,7 +397,7 @@ impl ShaderProcessor { pub fn process( &self, shader: &Shader, - shader_defs: &[String], + shader_defs: &[ShaderDefVal], shaders: &HashMap, Shader>, import_handles: &HashMap>, ) -> Result { @@ -405,16 +412,81 @@ impl ShaderProcessor { } }; - let shader_defs_unique = HashSet::::from_iter(shader_defs.iter().cloned()); + let shader_defs_unique = + HashMap::::from_iter(shader_defs.iter().map(|v| match v { + ShaderDefVal::Bool(k, _) | ShaderDefVal::Int(k, _) | ShaderDefVal::UInt(k, _) => { + (k.clone(), v.clone()) + } + })); let mut scopes = vec![true]; let mut final_string = String::new(); for line in shader_str.lines() { if let Some(cap) = self.ifdef_regex.captures(line) { let def = cap.get(1).unwrap(); - scopes.push(*scopes.last().unwrap() && shader_defs_unique.contains(def.as_str())); + scopes + .push(*scopes.last().unwrap() && shader_defs_unique.contains_key(def.as_str())); } else if let Some(cap) = self.ifndef_regex.captures(line) { let def = cap.get(1).unwrap(); - scopes.push(*scopes.last().unwrap() && !shader_defs_unique.contains(def.as_str())); + scopes.push( + *scopes.last().unwrap() && !shader_defs_unique.contains_key(def.as_str()), + ); + } else if let Some(cap) = self.ifop_regex.captures(line) { + let def = cap.get(1).unwrap(); + let op = cap.get(2).unwrap(); + let val = cap.get(3).unwrap(); + + fn act_on(a: T, b: T, op: &str) -> Result { + match op { + "==" => Ok(a == b), + "!=" => Ok(a != b), + ">" => Ok(a > b), + ">=" => Ok(a >= b), + "<" => Ok(a < b), + "<=" => Ok(a <= b), + _ => Err(ProcessShaderError::UnknownShaderDefOperator { + operator: op.to_string(), + }), + } + } + + let def = shader_defs_unique.get(def.as_str()).ok_or( + ProcessShaderError::UnknownShaderDef { + shader_def_name: def.as_str().to_string(), + }, + )?; + let new_scope = match def { + ShaderDefVal::Bool(name, def) => { + let val = val.as_str().parse().map_err(|_| { + ProcessShaderError::InvalidShaderDefComparisonValue { + shader_def_name: name.clone(), + value: val.as_str().to_string(), + expected: "bool".to_string(), + } + })?; + act_on(*def, val, op.as_str())? + } + ShaderDefVal::Int(name, def) => { + let val = val.as_str().parse().map_err(|_| { + ProcessShaderError::InvalidShaderDefComparisonValue { + shader_def_name: name.clone(), + value: val.as_str().to_string(), + expected: "int".to_string(), + } + })?; + act_on(*def, val, op.as_str())? + } + ShaderDefVal::UInt(name, def) => { + let val = val.as_str().parse().map_err(|_| { + ProcessShaderError::InvalidShaderDefComparisonValue { + shader_def_name: name.clone(), + value: val.as_str().to_string(), + expected: "uint".to_string(), + } + })?; + act_on(*def, val, op.as_str())? + } + }; + scopes.push(*scopes.last().unwrap() && new_scope); } else if self.else_regex.is_match(line) { let mut is_parent_scope_truthy = true; if scopes.len() > 1 { @@ -461,7 +533,26 @@ impl ShaderProcessor { { // ignore import path lines } else { - final_string.push_str(line); + let mut line_with_defs = line.to_string(); + for capture in self.def_regex.captures_iter(line) { + let def = capture.get(1).unwrap(); + if let Some(def) = shader_defs_unique.get(def.as_str()) { + line_with_defs = self + .def_regex + .replace(&line_with_defs, def.value_as_string()) + .to_string(); + } + } + for capture in self.def_regex_delimited.captures_iter(line) { + let def = capture.get(1).unwrap(); + if let Some(def) = shader_defs_unique.get(def.as_str()) { + line_with_defs = self + .def_regex_delimited + .replace(&line_with_defs, def.value_as_string()) + .to_string(); + } + } + final_string.push_str(&line_with_defs); final_string.push('\n'); } } @@ -488,7 +579,7 @@ impl ShaderProcessor { shaders: &HashMap, Shader>, import: &ShaderImport, shader: &Shader, - shader_defs: &[String], + shader_defs: &[ShaderDefVal], final_string: &mut String, ) -> Result<(), ProcessShaderError> { let imported_shader = import_handles @@ -557,7 +648,9 @@ mod tests { use bevy_utils::HashMap; use naga::ShaderStage; - use crate::render_resource::{ProcessShaderError, Shader, ShaderImport, ShaderProcessor}; + use crate::render_resource::{ + ProcessShaderError, Shader, ShaderDefVal, ShaderImport, ShaderProcessor, + }; #[rustfmt::skip] const WGSL: &str = r" struct View { @@ -723,7 +816,7 @@ fn vertex( let result = processor .process( &Shader::from_wgsl(WGSL), - &["TEXTURE".to_string()], + &["TEXTURE".into()], &HashMap::default(), &HashMap::default(), ) @@ -968,7 +1061,7 @@ fn vertex( let result = processor .process( &Shader::from_wgsl(WGSL_NESTED_IFDEF), - &["TEXTURE".to_string()], + &["TEXTURE".into()], &HashMap::default(), &HashMap::default(), ) @@ -1010,7 +1103,7 @@ fn vertex( let result = processor .process( &Shader::from_wgsl(WGSL_NESTED_IFDEF_ELSE), - &["TEXTURE".to_string()], + &["TEXTURE".into()], &HashMap::default(), &HashMap::default(), ) @@ -1130,7 +1223,7 @@ fn vertex( let result = processor .process( &Shader::from_wgsl(WGSL_NESTED_IFDEF), - &["ATTRIBUTE".to_string()], + &["ATTRIBUTE".into()], &HashMap::default(), &HashMap::default(), ) @@ -1172,7 +1265,7 @@ fn vertex( let result = processor .process( &Shader::from_wgsl(WGSL_NESTED_IFDEF), - &["TEXTURE".to_string(), "ATTRIBUTE".to_string()], + &["TEXTURE".into(), "ATTRIBUTE".into()], &HashMap::default(), &HashMap::default(), ) @@ -1219,7 +1312,7 @@ fn in_main_present() { } let result = processor .process( &Shader::from_wgsl(INPUT), - &["MAIN_PRESENT".to_string(), "IMPORT_PRESENT".to_string()], + &["MAIN_PRESENT".into(), "IMPORT_PRESENT".into()], &shaders, &import_handles, ) @@ -1274,7 +1367,7 @@ fn in_main() { } let result = processor .process( &Shader::from_wgsl(INPUT), - &["DEEP".to_string()], + &["DEEP".into()], &shaders, &import_handles, ) @@ -1332,7 +1425,7 @@ fn baz() { } let result = processor .process( &Shader::from_wgsl(INPUT), - &["FOO".to_string()], + &["FOO".into()], &shaders, &import_handles, ) @@ -1344,4 +1437,515 @@ fn baz() { } .unwrap(); assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); } + + #[test] + fn process_shader_def_unknown_operator() { + #[rustfmt::skip] + const WGSL: &str = r" +struct View { + view_proj: mat4x4, + world_position: vec3, +}; +@group(0) @binding(0) +var view: View; + +#if TEXTURE !! true +@group(1) @binding(0) +var sprite_texture: texture_2d; +#endif + +struct VertexOutput { + @location(0) uv: vec2, + @builtin(position) position: vec4, +}; + +@vertex +fn vertex( + @location(0) vertex_position: vec3, + @location(1) vertex_uv: vec2 +) -> VertexOutput { + var out: VertexOutput; + out.uv = vertex_uv; + out.position = view.view_proj * vec4(vertex_position, 1.0); + return out; +} +"; + + let processor = ShaderProcessor::default(); + + let result_missing = processor.process( + &Shader::from_wgsl(WGSL), + &["TEXTURE".into()], + &HashMap::default(), + &HashMap::default(), + ); + assert_eq!( + result_missing, + Err(ProcessShaderError::UnknownShaderDefOperator { + operator: "!!".to_string() + }) + ); + } + #[test] + fn process_shader_def_equal_int() { + #[rustfmt::skip] + const WGSL: &str = r" +struct View { + view_proj: mat4x4, + world_position: vec3, +}; +@group(0) @binding(0) +var view: View; + +#if TEXTURE == 3 +@group(1) @binding(0) +var sprite_texture: texture_2d; +#endif + +struct VertexOutput { + @location(0) uv: vec2, + @builtin(position) position: vec4, +}; + +@vertex +fn vertex( + @location(0) vertex_position: vec3, + @location(1) vertex_uv: vec2 +) -> VertexOutput { + var out: VertexOutput; + out.uv = vertex_uv; + out.position = view.view_proj * vec4(vertex_position, 1.0); + return out; +} +"; + + #[rustfmt::skip] + const EXPECTED_EQ: &str = r" +struct View { + view_proj: mat4x4, + world_position: vec3, +}; +@group(0) @binding(0) +var view: View; + +@group(1) @binding(0) +var sprite_texture: texture_2d; + +struct VertexOutput { + @location(0) uv: vec2, + @builtin(position) position: vec4, +}; + +@vertex +fn vertex( + @location(0) vertex_position: vec3, + @location(1) vertex_uv: vec2 +) -> VertexOutput { + var out: VertexOutput; + out.uv = vertex_uv; + out.position = view.view_proj * vec4(vertex_position, 1.0); + return out; +} +"; + + #[rustfmt::skip] + const EXPECTED_NEQ: &str = r" +struct View { + view_proj: mat4x4, + world_position: vec3, +}; +@group(0) @binding(0) +var view: View; + + +struct VertexOutput { + @location(0) uv: vec2, + @builtin(position) position: vec4, +}; + +@vertex +fn vertex( + @location(0) vertex_position: vec3, + @location(1) vertex_uv: vec2 +) -> VertexOutput { + var out: VertexOutput; + out.uv = vertex_uv; + out.position = view.view_proj * vec4(vertex_position, 1.0); + return out; +} +"; + let processor = ShaderProcessor::default(); + let result_eq = processor + .process( + &Shader::from_wgsl(WGSL), + &[ShaderDefVal::Int("TEXTURE".to_string(), 3)], + &HashMap::default(), + &HashMap::default(), + ) + .unwrap(); + assert_eq!(result_eq.get_wgsl_source().unwrap(), EXPECTED_EQ); + + let result_neq = processor + .process( + &Shader::from_wgsl(WGSL), + &[ShaderDefVal::Int("TEXTURE".to_string(), 7)], + &HashMap::default(), + &HashMap::default(), + ) + .unwrap(); + assert_eq!(result_neq.get_wgsl_source().unwrap(), EXPECTED_NEQ); + + let result_missing = processor.process( + &Shader::from_wgsl(WGSL), + &[], + &HashMap::default(), + &HashMap::default(), + ); + assert_eq!( + result_missing, + Err(ProcessShaderError::UnknownShaderDef { + shader_def_name: "TEXTURE".to_string() + }) + ); + + let result_wrong_type = processor.process( + &Shader::from_wgsl(WGSL), + &[ShaderDefVal::Bool("TEXTURE".to_string(), true)], + &HashMap::default(), + &HashMap::default(), + ); + assert_eq!( + result_wrong_type, + Err(ProcessShaderError::InvalidShaderDefComparisonValue { + shader_def_name: "TEXTURE".to_string(), + expected: "bool".to_string(), + value: "3".to_string() + }) + ); + } + + #[test] + fn process_shader_def_equal_bool() { + #[rustfmt::skip] + const WGSL: &str = r" +struct View { + view_proj: mat4x4, + world_position: vec3, +}; +@group(0) @binding(0) +var view: View; + +#if TEXTURE == true +@group(1) @binding(0) +var sprite_texture: texture_2d; +#endif + +struct VertexOutput { + @location(0) uv: vec2, + @builtin(position) position: vec4, +}; + +@vertex +fn vertex( + @location(0) vertex_position: vec3, + @location(1) vertex_uv: vec2 +) -> VertexOutput { + var out: VertexOutput; + out.uv = vertex_uv; + out.position = view.view_proj * vec4(vertex_position, 1.0); + return out; +} +"; + + #[rustfmt::skip] + const EXPECTED_EQ: &str = r" +struct View { + view_proj: mat4x4, + world_position: vec3, +}; +@group(0) @binding(0) +var view: View; + +@group(1) @binding(0) +var sprite_texture: texture_2d; + +struct VertexOutput { + @location(0) uv: vec2, + @builtin(position) position: vec4, +}; + +@vertex +fn vertex( + @location(0) vertex_position: vec3, + @location(1) vertex_uv: vec2 +) -> VertexOutput { + var out: VertexOutput; + out.uv = vertex_uv; + out.position = view.view_proj * vec4(vertex_position, 1.0); + return out; +} +"; + + #[rustfmt::skip] + const EXPECTED_NEQ: &str = r" +struct View { + view_proj: mat4x4, + world_position: vec3, +}; +@group(0) @binding(0) +var view: View; + + +struct VertexOutput { + @location(0) uv: vec2, + @builtin(position) position: vec4, +}; + +@vertex +fn vertex( + @location(0) vertex_position: vec3, + @location(1) vertex_uv: vec2 +) -> VertexOutput { + var out: VertexOutput; + out.uv = vertex_uv; + out.position = view.view_proj * vec4(vertex_position, 1.0); + return out; +} +"; + let processor = ShaderProcessor::default(); + let result_eq = processor + .process( + &Shader::from_wgsl(WGSL), + &[ShaderDefVal::Bool("TEXTURE".to_string(), true)], + &HashMap::default(), + &HashMap::default(), + ) + .unwrap(); + assert_eq!(result_eq.get_wgsl_source().unwrap(), EXPECTED_EQ); + + let result_neq = processor + .process( + &Shader::from_wgsl(WGSL), + &[ShaderDefVal::Bool("TEXTURE".to_string(), false)], + &HashMap::default(), + &HashMap::default(), + ) + .unwrap(); + assert_eq!(result_neq.get_wgsl_source().unwrap(), EXPECTED_NEQ); + } + + #[test] + fn process_shader_def_not_equal_bool() { + #[rustfmt::skip] + const WGSL: &str = r" +struct View { + view_proj: mat4x4, + world_position: vec3, +}; +@group(0) @binding(0) +var view: View; + +#if TEXTURE != false +@group(1) @binding(0) +var sprite_texture: texture_2d; +#endif + +struct VertexOutput { + @location(0) uv: vec2, + @builtin(position) position: vec4, +}; + +@vertex +fn vertex( + @location(0) vertex_position: vec3, + @location(1) vertex_uv: vec2 +) -> VertexOutput { + var out: VertexOutput; + out.uv = vertex_uv; + out.position = view.view_proj * vec4(vertex_position, 1.0); + return out; +} +"; + + #[rustfmt::skip] + const EXPECTED_EQ: &str = r" +struct View { + view_proj: mat4x4, + world_position: vec3, +}; +@group(0) @binding(0) +var view: View; + +@group(1) @binding(0) +var sprite_texture: texture_2d; + +struct VertexOutput { + @location(0) uv: vec2, + @builtin(position) position: vec4, +}; + +@vertex +fn vertex( + @location(0) vertex_position: vec3, + @location(1) vertex_uv: vec2 +) -> VertexOutput { + var out: VertexOutput; + out.uv = vertex_uv; + out.position = view.view_proj * vec4(vertex_position, 1.0); + return out; +} +"; + + #[rustfmt::skip] + const EXPECTED_NEQ: &str = r" +struct View { + view_proj: mat4x4, + world_position: vec3, +}; +@group(0) @binding(0) +var view: View; + + +struct VertexOutput { + @location(0) uv: vec2, + @builtin(position) position: vec4, +}; + +@vertex +fn vertex( + @location(0) vertex_position: vec3, + @location(1) vertex_uv: vec2 +) -> VertexOutput { + var out: VertexOutput; + out.uv = vertex_uv; + out.position = view.view_proj * vec4(vertex_position, 1.0); + return out; +} +"; + let processor = ShaderProcessor::default(); + let result_eq = processor + .process( + &Shader::from_wgsl(WGSL), + &[ShaderDefVal::Bool("TEXTURE".to_string(), true)], + &HashMap::default(), + &HashMap::default(), + ) + .unwrap(); + assert_eq!(result_eq.get_wgsl_source().unwrap(), EXPECTED_EQ); + + let result_neq = processor + .process( + &Shader::from_wgsl(WGSL), + &[ShaderDefVal::Bool("TEXTURE".to_string(), false)], + &HashMap::default(), + &HashMap::default(), + ) + .unwrap(); + assert_eq!(result_neq.get_wgsl_source().unwrap(), EXPECTED_NEQ); + + let result_missing = processor.process( + &Shader::from_wgsl(WGSL), + &[], + &HashMap::default(), + &HashMap::default(), + ); + assert_eq!( + result_missing, + Err(ProcessShaderError::UnknownShaderDef { + shader_def_name: "TEXTURE".to_string() + }) + ); + + let result_wrong_type = processor.process( + &Shader::from_wgsl(WGSL), + &[ShaderDefVal::Int("TEXTURE".to_string(), 7)], + &HashMap::default(), + &HashMap::default(), + ); + assert_eq!( + result_wrong_type, + Err(ProcessShaderError::InvalidShaderDefComparisonValue { + shader_def_name: "TEXTURE".to_string(), + expected: "int".to_string(), + value: "false".to_string() + }) + ); + } + + #[test] + fn process_shader_def_replace() { + #[rustfmt::skip] + const WGSL: &str = r" +struct View { + view_proj: mat4x4, + world_position: vec3, +}; +@group(0) @binding(0) +var view: View; + +struct VertexOutput { + @location(0) uv: vec2, + @builtin(position) position: vec4, +}; + +@vertex +fn vertex( + @location(0) vertex_position: vec3, + @location(1) vertex_uv: vec2 +) -> VertexOutput { + var out: VertexOutput; + out.uv = vertex_uv; + var a: i32 = #FIRST_VALUE; + var b: i32 = #FIRST_VALUE * #SECOND_VALUE; + var c: i32 = #MISSING_VALUE; + var d: bool = #BOOL_VALUE; + out.position = view.view_proj * vec4(vertex_position, 1.0); + return out; +} +"; + + #[rustfmt::skip] + const EXPECTED_REPLACED: &str = r" +struct View { + view_proj: mat4x4, + world_position: vec3, +}; +@group(0) @binding(0) +var view: View; + +struct VertexOutput { + @location(0) uv: vec2, + @builtin(position) position: vec4, +}; + +@vertex +fn vertex( + @location(0) vertex_position: vec3, + @location(1) vertex_uv: vec2 +) -> VertexOutput { + var out: VertexOutput; + out.uv = vertex_uv; + var a: i32 = 5; + var b: i32 = 5 * 3; + var c: i32 = #MISSING_VALUE; + var d: bool = true; + out.position = view.view_proj * vec4(vertex_position, 1.0); + return out; +} +"; + let processor = ShaderProcessor::default(); + let result = processor + .process( + &Shader::from_wgsl(WGSL), + &[ + ShaderDefVal::Bool("BOOL_VALUE".to_string(), true), + ShaderDefVal::Int("FIRST_VALUE".to_string(), 5), + ShaderDefVal::Int("SECOND_VALUE".to_string(), 3), + ], + &HashMap::default(), + &HashMap::default(), + ) + .unwrap(); + assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED_REPLACED); + } } diff --git a/crates/bevy_render/src/render_resource/texture.rs b/crates/bevy_render/src/render_resource/texture.rs index 4a7d582d7559e..02cc818408073 100644 --- a/crates/bevy_render/src/render_resource/texture.rs +++ b/crates/bevy_render/src/render_resource/texture.rs @@ -1,9 +1,10 @@ -use bevy_utils::Uuid; -use std::{ops::Deref, sync::Arc}; +use crate::define_atomic_id; +use std::ops::Deref; -/// A [`Texture`] identifier. -#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] -pub struct TextureId(Uuid); +use crate::render_resource::resource_macros::*; + +define_atomic_id!(TextureId); +render_resource_wrapper!(ErasedTexture, wgpu::Texture); /// A GPU-accessible texture. /// @@ -12,7 +13,7 @@ pub struct TextureId(Uuid); #[derive(Clone, Debug)] pub struct Texture { id: TextureId, - value: Arc, + value: ErasedTexture, } impl Texture { @@ -31,8 +32,8 @@ impl Texture { impl From for Texture { fn from(value: wgpu::Texture) -> Self { Texture { - id: TextureId(Uuid::new_v4()), - value: Arc::new(value), + id: TextureId::new(), + value: ErasedTexture::new(value), } } } @@ -46,24 +47,24 @@ 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); /// This type combines wgpu's [`TextureView`](wgpu::TextureView) and /// [`SurfaceTexture`](wgpu::SurfaceTexture) into the same interface. #[derive(Clone, Debug)] pub enum TextureViewValue { /// The value is an actual wgpu [`TextureView`](wgpu::TextureView). - TextureView(Arc), + TextureView(ErasedTextureView), /// The value is a wgpu [`SurfaceTexture`](wgpu::SurfaceTexture), but dereferences to /// a [`TextureView`](wgpu::TextureView). SurfaceTexture { // NOTE: The order of these fields is important because the view must be dropped before the // frame is dropped - view: Arc, - texture: Arc, + view: ErasedTextureView, + texture: ErasedSurfaceTexture, }, } @@ -89,7 +90,7 @@ impl TextureView { pub fn take_surface_texture(self) -> Option { match self.value { TextureViewValue::TextureView(_) => None, - TextureViewValue::SurfaceTexture { texture, .. } => Arc::try_unwrap(texture).ok(), + TextureViewValue::SurfaceTexture { texture, .. } => texture.try_unwrap(), } } } @@ -97,19 +98,19 @@ impl TextureView { impl From for TextureView { fn from(value: wgpu::TextureView) -> Self { TextureView { - id: TextureViewId(Uuid::new_v4()), - value: TextureViewValue::TextureView(Arc::new(value)), + id: TextureViewId::new(), + value: TextureViewValue::TextureView(ErasedTextureView::new(value)), } } } impl From for TextureView { fn from(value: wgpu::SurfaceTexture) -> Self { - let texture = Arc::new(value); - let view = Arc::new(texture.texture.create_view(&Default::default())); + let view = ErasedTextureView::new(value.texture.create_view(&Default::default())); + let texture = ErasedSurfaceTexture::new(value); TextureView { - id: TextureViewId(Uuid::new_v4()), + id: TextureViewId::new(), value: TextureViewValue::SurfaceTexture { texture, view }, } } @@ -127,9 +128,8 @@ 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`]. /// They define image filters (including anisotropy) and address (wrapping) modes, among other things. @@ -139,7 +139,7 @@ pub struct SamplerId(Uuid); #[derive(Clone, Debug)] pub struct Sampler { id: SamplerId, - value: Arc, + value: ErasedSampler, } impl Sampler { @@ -153,8 +153,8 @@ impl Sampler { impl From for Sampler { fn from(value: wgpu::Sampler) -> Self { Sampler { - id: SamplerId(Uuid::new_v4()), - value: Arc::new(value), + id: SamplerId::new(), + value: ErasedSampler::new(value), } } } diff --git a/crates/bevy_render/src/renderer/graph_runner.rs b/crates/bevy_render/src/renderer/graph_runner.rs index 3ed1b0e971fd5..1513d1c4e5697 100644 --- a/crates/bevy_render/src/renderer/graph_runner.rs +++ b/crates/bevy_render/src/renderer/graph_runner.rs @@ -98,7 +98,7 @@ impl RenderGraphRunner { .collect(); // pass inputs into the graph - if let Some(input_node) = graph.input_node() { + if let Some(input_node) = graph.get_input_node() { let mut input_values: SmallVec<[SlotValue; 4]> = SmallVec::new(); for (i, input_slot) in input_node.input_slots.iter().enumerate() { if let Some(input_value) = inputs.get(i) { diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index 3d74ecce4c2b2..aa362a3d9b2ed 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 @@ -261,8 +261,6 @@ pub async fn initialize_renderer( ) .await .unwrap(); - - let device = Arc::new(device); let queue = Arc::new(queue); let adapter = Arc::new(adapter); ( diff --git a/crates/bevy_render/src/renderer/render_device.rs b/crates/bevy_render/src/renderer/render_device.rs index 0ac5acbc6fa84..eef8b100cb130 100644 --- a/crates/bevy_render/src/renderer/render_device.rs +++ b/crates/bevy_render/src/renderer/render_device.rs @@ -3,20 +3,25 @@ use crate::render_resource::{ RenderPipeline, Sampler, Texture, }; use bevy_ecs::system::Resource; -use std::sync::Arc; use wgpu::{util::DeviceExt, BufferAsyncError, BufferBindingType}; use super::RenderQueue; +use crate::render_resource::resource_macros::*; + +render_resource_wrapper!(ErasedRenderDevice, wgpu::Device); + /// This GPU device is responsible for the creation of most rendering and compute resources. #[derive(Resource, Clone)] pub struct RenderDevice { - device: Arc, + device: ErasedRenderDevice, } -impl From> for RenderDevice { - fn from(device: Arc) -> Self { - Self { device } +impl From for RenderDevice { + fn from(device: wgpu::Device) -> Self { + Self { + device: ErasedRenderDevice::new(device), + } } } diff --git a/crates/bevy_render/src/settings.rs b/crates/bevy_render/src/settings.rs index 52c15fad7720b..62bdc7be43ba2 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 6eedd7cbf5992..b2d50da6097f6 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/texture/image.rs b/crates/bevy_render/src/texture/image.rs index 1562d887e0404..d2cfa72c80d26 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -18,10 +18,7 @@ use bevy_math::Vec2; use bevy_reflect::{FromReflect, Reflect, TypeUuid}; use std::hash::Hash; use thiserror::Error; -use wgpu::{ - Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, TextureDimension, TextureFormat, - TextureViewDescriptor, -}; +use wgpu::{Extent3d, TextureDimension, TextureFormat, TextureViewDescriptor}; pub const TEXTURE_ASSET_INDEX: u64 = 0; pub const SAMPLER_ASSET_INDEX: u64 = 1; @@ -467,132 +464,18 @@ impl Volume for Extent3d { } } -/// Information about the pixel size in bytes and the number of different components. -pub struct PixelInfo { - /// The size of a component of a pixel in bytes. - pub type_size: usize, - /// The amount of different components (color channels). - pub num_components: usize, -} - /// Extends the wgpu [`TextureFormat`] with information about the pixel. pub trait TextureFormatPixelInfo { - /// Returns the pixel information of the format. - fn pixel_info(&self) -> PixelInfo; - /// Returns the size of a pixel of the format. - fn pixel_size(&self) -> usize { - let info = self.pixel_info(); - info.type_size * info.num_components - } + /// Returns the size of a pixel in bytes of the format. + fn pixel_size(&self) -> usize; } impl TextureFormatPixelInfo for TextureFormat { - #[allow(clippy::match_same_arms)] - fn pixel_info(&self) -> PixelInfo { - let type_size = match self { - // 8bit - TextureFormat::R8Unorm - | TextureFormat::R8Snorm - | TextureFormat::R8Uint - | TextureFormat::R8Sint - | TextureFormat::Rg8Unorm - | TextureFormat::Rg8Snorm - | TextureFormat::Rg8Uint - | TextureFormat::Rg8Sint - | TextureFormat::Rgba8Unorm - | TextureFormat::Rgba8UnormSrgb - | TextureFormat::Rgba8Snorm - | TextureFormat::Rgba8Uint - | TextureFormat::Rgba8Sint - | TextureFormat::Bgra8Unorm - | TextureFormat::Bgra8UnormSrgb => 1, - - // 16bit - TextureFormat::R16Uint - | TextureFormat::R16Sint - | TextureFormat::R16Float - | TextureFormat::R16Unorm - | TextureFormat::Rg16Uint - | TextureFormat::Rg16Sint - | TextureFormat::Rg16Unorm - | TextureFormat::Rg16Float - | TextureFormat::Rgba16Uint - | TextureFormat::Rgba16Sint - | TextureFormat::Rgba16Float => 2, - - // 32bit - TextureFormat::R32Uint - | TextureFormat::R32Sint - | TextureFormat::R32Float - | TextureFormat::Rg32Uint - | TextureFormat::Rg32Sint - | TextureFormat::Rg32Float - | TextureFormat::Rgba32Uint - | TextureFormat::Rgba32Sint - | TextureFormat::Rgba32Float - | TextureFormat::Depth32Float => 4, - - // special cases - TextureFormat::Rgb10a2Unorm => 4, - TextureFormat::Rg11b10Float => 4, - TextureFormat::Depth24Plus => 3, // FIXME is this correct? - TextureFormat::Depth24PlusStencil8 => 4, - // TODO: this is not good! this is a temporary step while porting bevy_render to direct wgpu usage - _ => panic!("cannot get pixel info for type"), - }; - - let components = match self { - TextureFormat::R8Unorm - | TextureFormat::R8Snorm - | TextureFormat::R8Uint - | TextureFormat::R8Sint - | TextureFormat::R16Uint - | TextureFormat::R16Sint - | TextureFormat::R16Unorm - | TextureFormat::R16Float - | TextureFormat::R32Uint - | TextureFormat::R32Sint - | TextureFormat::R32Float => 1, - - TextureFormat::Rg8Unorm - | TextureFormat::Rg8Snorm - | TextureFormat::Rg8Uint - | TextureFormat::Rg8Sint - | TextureFormat::Rg16Uint - | TextureFormat::Rg16Sint - | TextureFormat::Rg16Unorm - | TextureFormat::Rg16Float - | TextureFormat::Rg32Uint - | TextureFormat::Rg32Sint - | TextureFormat::Rg32Float => 2, - - TextureFormat::Rgba8Unorm - | TextureFormat::Rgba8UnormSrgb - | TextureFormat::Rgba8Snorm - | TextureFormat::Rgba8Uint - | TextureFormat::Rgba8Sint - | TextureFormat::Bgra8Unorm - | TextureFormat::Bgra8UnormSrgb - | TextureFormat::Rgba16Uint - | TextureFormat::Rgba16Sint - | TextureFormat::Rgba16Float - | TextureFormat::Rgba32Uint - | TextureFormat::Rgba32Sint - | TextureFormat::Rgba32Float => 4, - - // special cases - TextureFormat::Rgb10a2Unorm - | TextureFormat::Rg11b10Float - | TextureFormat::Depth32Float - | TextureFormat::Depth24Plus - | TextureFormat::Depth24PlusStencil8 => 1, - // TODO: this is not good! this is a temporary step while porting bevy_render to direct wgpu usage - _ => panic!("cannot get pixel info for type"), - }; - - PixelInfo { - type_size, - num_components: components, + fn pixel_size(&self) -> usize { + let info = self.describe(); + match info.block_dimensions { + (1, 1) => info.block_size.into(), + _ => panic!("Using pixel_size for compressed textures is invalid"), } } } @@ -627,41 +510,11 @@ impl RenderAsset for Image { image: Self::ExtractedAsset, (render_device, render_queue, default_sampler): &mut SystemParamItem, ) -> Result> { - let texture = if image.texture_descriptor.mip_level_count > 1 || image.is_compressed() { - render_device.create_texture_with_data( - render_queue, - &image.texture_descriptor, - &image.data, - ) - } else { - let texture = render_device.create_texture(&image.texture_descriptor); - let format_size = image.texture_descriptor.format.pixel_size(); - render_queue.write_texture( - ImageCopyTexture { - texture: &texture, - mip_level: 0, - origin: Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }, - &image.data, - ImageDataLayout { - offset: 0, - bytes_per_row: Some( - std::num::NonZeroU32::new( - image.texture_descriptor.size.width * format_size as u32, - ) - .unwrap(), - ), - rows_per_image: if image.texture_descriptor.size.depth_or_array_layers > 1 { - std::num::NonZeroU32::new(image.texture_descriptor.size.height) - } else { - None - }, - }, - image.texture_descriptor.size, - ); - texture - }; + let texture = render_device.create_texture_with_data( + render_queue, + &image.texture_descriptor, + &image.data, + ); let texture_view = texture.create_view( image diff --git a/crates/bevy_render/src/texture/image_texture_conversion.rs b/crates/bevy_render/src/texture/image_texture_conversion.rs index 45ae5bbc22582..de8ed285f724e 100644 --- a/crates/bevy_render/src/texture/image_texture_conversion.rs +++ b/crates/bevy_render/src/texture/image_texture_conversion.rs @@ -126,7 +126,7 @@ impl Image { let r = pixel[0]; let g = pixel[1]; let b = pixel[2]; - let a = u16::max_value(); + let a = 1f32; local_data.extend_from_slice(&r.to_ne_bytes()); local_data.extend_from_slice(&g.to_ne_bytes()); diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index 596b586f44cba..89637d730b2fc 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -86,6 +86,7 @@ impl Plugin for ImagePlugin { app.add_plugin(RenderAssetPlugin::::with_prepare_asset_label( PrepareAssetLabel::PreAssetPrepare, )) + .register_type::() .add_asset::() .register_asset_reflect::(); app.world diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 843b0c638cb36..bc0c12b023135 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -31,7 +31,12 @@ pub struct ViewPlugin; impl Plugin for ViewPlugin { fn build(&self, app: &mut App) { - app.register_type::() + app.register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() .init_resource::() // NOTE: windows.is_changed() handles cases where a window was resized .add_plugin(ExtractResourcePlugin::::default()) diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index c08df608985d1..e4a57217c1bc1 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -23,40 +23,48 @@ 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 } } bitflags::bitflags! { #[derive(Reflect)] - struct ComputedVisibilityFlags: u8 { + pub(super) struct ComputedVisibilityFlags: u8 { const VISIBLE_IN_VIEW = 1 << 0; const VISIBLE_IN_HIERARCHY = 1 << 1; } @@ -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.rs b/crates/bevy_scene/src/dynamic_scene.rs index 92f8cae312f4e..c0eb067b2ca8b 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -46,7 +46,7 @@ impl DynamicScene { let mut builder = DynamicSceneBuilder::from_world_with_type_registry(world, type_registry.clone()); - builder.extract_entities(world.iter_entities()); + builder.extract_entities(world.iter_entities().map(|entity| entity.id())); builder.build() } diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 841af6756f3ba..5ea65a8714eab 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_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 1a377c6b7ae07..f8f581a609d65 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -54,10 +54,11 @@ pub enum SceneSpawnError { } impl SceneSpawner { - pub fn spawn_dynamic(&mut self, scene_handle: Handle) { + pub fn spawn_dynamic(&mut self, scene_handle: Handle) -> InstanceId { let instance_id = InstanceId::new(); self.dynamic_scenes_to_spawn .push((scene_handle, instance_id)); + instance_id } pub fn spawn_dynamic_as_child( diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 72dac1b39095e..0f3b86bf9e1d5 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -320,10 +320,7 @@ pub fn queue_material2d_meshes( } for (view, visible_entities, tonemapping, mut transparent_phase) in &mut views { - let draw_transparent_pbr = transparent_draw_functions - .read() - .get_id::>() - .unwrap(); + let draw_transparent_pbr = transparent_draw_functions.read().id::>(); let mut view_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples) | Mesh2dPipelineKey::from_hdr(view.hdr); diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 5483c2b98d972..0910463f26c9f 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -351,36 +351,36 @@ impl SpecializedMeshPipeline for Mesh2dPipeline { let mut vertex_attributes = Vec::new(); if layout.contains(Mesh::ATTRIBUTE_POSITION) { - shader_defs.push(String::from("VERTEX_POSITIONS")); + shader_defs.push("VERTEX_POSITIONS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0)); } if layout.contains(Mesh::ATTRIBUTE_NORMAL) { - shader_defs.push(String::from("VERTEX_NORMALS")); + shader_defs.push("VERTEX_NORMALS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(1)); } if layout.contains(Mesh::ATTRIBUTE_UV_0) { - shader_defs.push(String::from("VERTEX_UVS")); + shader_defs.push("VERTEX_UVS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(2)); } if layout.contains(Mesh::ATTRIBUTE_TANGENT) { - shader_defs.push(String::from("VERTEX_TANGENTS")); + shader_defs.push("VERTEX_TANGENTS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3)); } if layout.contains(Mesh::ATTRIBUTE_COLOR) { - shader_defs.push(String::from("VERTEX_COLORS")); + shader_defs.push("VERTEX_COLORS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(4)); } if key.contains(Mesh2dPipelineKey::TONEMAP_IN_SHADER) { - shader_defs.push("TONEMAP_IN_SHADER".to_string()); + shader_defs.push("TONEMAP_IN_SHADER".into()); // Debanding is tied to tonemapping in the shader, cannot run without it. if key.contains(Mesh2dPipelineKey::DEBAND_DITHER) { - shader_defs.push("DEBAND_DITHER".to_string()); + shader_defs.push("DEBAND_DITHER".into()); } } diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d_view_types.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d_view_types.wgsl index 1759fa499fd07..78c3459235a16 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_view_types.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_view_types.wgsl @@ -21,4 +21,8 @@ struct Globals { // Frame count since the start of the app. // It wraps to zero when it reaches the maximum value of a u32. frame_count: u32, +#ifdef SIXTEEN_BYTE_ALIGNMENT + // WebGL2 structs must be 16 byte aligned. + _wasm_padding: f32 +#endif } diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 8b71558813973..765679b09b425 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -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 { @@ -208,15 +212,15 @@ impl SpecializedRenderPipeline for SpritePipeline { let mut shader_defs = Vec::new(); if key.contains(SpritePipelineKey::COLORED) { - shader_defs.push("COLORED".to_string()); + shader_defs.push("COLORED".into()); } if key.contains(SpritePipelineKey::TONEMAP_IN_SHADER) { - shader_defs.push("TONEMAP_IN_SHADER".to_string()); + shader_defs.push("TONEMAP_IN_SHADER".into()); // Debanding is tied to tonemapping in the shader, cannot run without it. if key.contains(SpritePipelineKey::DEBAND_DITHER) { - shader_defs.push("DEBAND_DITHER".to_string()); + shader_defs.push("DEBAND_DITHER".into()); } } @@ -488,7 +492,7 @@ pub fn queue_sprites( layout: &sprite_pipeline.view_layout, })); - let draw_sprite_function = draw_functions.read().get_id::().unwrap(); + let draw_sprite_function = draw_functions.read().id::(); // Vertex buffer indices let mut index = 0; @@ -543,7 +547,7 @@ pub fn queue_sprites( image_handle_id: HandleId::Id(Uuid::nil(), u64::MAX), colored: false, }; - let mut current_batch_entity = Entity::from_raw(u32::MAX); + let mut current_batch_entity = Entity::PLACEHOLDER; let mut current_image_size = Vec2::ZERO; // Add a phase item for each sprite, and detect when succesive items can be batched. // Spawn an entity with a `SpriteBatch` component for each possible batch. diff --git a/crates/bevy_tasks/Cargo.toml b/crates/bevy_tasks/Cargo.toml index f86fb78d3bc23..e39e182dffedc 100644 --- a/crates/bevy_tasks/Cargo.toml +++ b/crates/bevy_tasks/Cargo.toml @@ -14,7 +14,7 @@ async-executor = "1.3.0" async-channel = "1.4.2" async-task = "4.2.0" once_cell = "1.7" -concurrent-queue = "1.2.2" +concurrent-queue = "2.0.0" [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen-futures = "0.4" diff --git a/crates/bevy_tasks/src/task_pool.rs b/crates/bevy_tasks/src/task_pool.rs index 39308c2bfd7bc..83bc92843f00d 100644 --- a/crates/bevy_tasks/src/task_pool.rs +++ b/crates/bevy_tasks/src/task_pool.rs @@ -2,18 +2,28 @@ use std::{ future::Future, marker::PhantomData, mem, - pin::Pin, sync::Arc, thread::{self, JoinHandle}, }; +use async_task::FallibleTask; use concurrent_queue::ConcurrentQueue; 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 { @@ -248,8 +281,8 @@ impl TaskPool { let task_scope_executor = &async_executor::Executor::default(); let task_scope_executor: &'env async_executor::Executor = unsafe { mem::transmute(task_scope_executor) }; - let spawned: ConcurrentQueue> = ConcurrentQueue::unbounded(); - let spawned_ref: &'env ConcurrentQueue> = + let spawned: ConcurrentQueue> = ConcurrentQueue::unbounded(); + let spawned_ref: &'env ConcurrentQueue> = unsafe { mem::transmute(&spawned) }; let scope = Scope { @@ -267,10 +300,10 @@ impl TaskPool { if spawned.is_empty() { Vec::new() } else { - let get_results = async move { - let mut results = Vec::with_capacity(spawned.len()); - while let Ok(task) = spawned.pop() { - results.push(task.await); + let get_results = async { + let mut results = Vec::with_capacity(spawned_ref.len()); + while let Ok(task) = spawned_ref.pop() { + results.push(task.await.unwrap()); } results @@ -279,28 +312,16 @@ impl TaskPool { // Pin the futures on the stack. pin!(get_results); - // SAFETY: This function blocks until all futures complete, so we do not read/write - // the data from futures outside of the 'scope lifetime. However, - // rust has no way of knowing this so we must convert to 'static - // here to appease the compiler as it is unable to validate safety. - let get_results: Pin<&mut (dyn Future> + 'static + Send)> = get_results; - let get_results: Pin<&'static mut (dyn Future> + 'static + Send)> = - unsafe { mem::transmute(get_results) }; - - // The thread that calls scope() will participate in driving tasks in the pool - // forward until the tasks that are spawned by this scope() call - // complete. (If the caller of scope() happens to be a thread in - // this thread pool, and we only have one thread in the pool, then - // simply calling future::block_on(spawned) would deadlock.) - let mut spawned = task_scope_executor.spawn(get_results); - loop { - if let Some(result) = future::block_on(future::poll_once(&mut spawned)) { + if let Some(result) = future::block_on(future::poll_once(&mut get_results)) { break result; }; - self.executor.try_tick(); - task_scope_executor.try_tick(); + std::panic::catch_unwind(|| { + executor.try_tick(); + task_scope_executor.try_tick(); + }) + .ok(); } } } @@ -375,7 +396,7 @@ impl Drop for TaskPool { pub struct Scope<'scope, 'env: 'scope, T> { executor: &'scope async_executor::Executor<'scope>, task_scope_executor: &'scope async_executor::Executor<'scope>, - spawned: &'scope ConcurrentQueue>, + spawned: &'scope ConcurrentQueue>, // make `Scope` invariant over 'scope and 'env scope: PhantomData<&'scope mut &'scope ()>, env: PhantomData<&'env mut &'env ()>, @@ -391,7 +412,7 @@ impl<'scope, 'env, T: Send + 'scope> Scope<'scope, 'env, T> { /// /// For more information, see [`TaskPool::scope`]. pub fn spawn + 'scope + Send>(&self, f: Fut) { - let task = self.executor.spawn(f); + let task = self.executor.spawn(f).fallible(); // ConcurrentQueue only errors when closed or full, but we never // close and use an unbouded queue, so it is safe to unwrap self.spawned.push(task).unwrap(); @@ -404,13 +425,26 @@ impl<'scope, 'env, T: Send + 'scope> Scope<'scope, 'env, T> { /// /// For more information, see [`TaskPool::scope`]. pub fn spawn_on_scope + 'scope + Send>(&self, f: Fut) { - let task = self.task_scope_executor.spawn(f); + let task = self.task_scope_executor.spawn(f).fallible(); // ConcurrentQueue only errors when closed or full, but we never // close and use an unbouded queue, so it is safe to unwrap self.spawned.push(task).unwrap(); } } +impl<'scope, 'env, T> Drop for Scope<'scope, 'env, T> +where + T: 'scope, +{ + fn drop(&mut self) { + future::block_on(async { + while let Ok(task) = self.spawned.pop() { + task.cancel().await; + } + }); + } +} + #[cfg(test)] #[allow(clippy::disallowed_types)] mod tests { @@ -451,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_text/src/error.rs b/crates/bevy_text/src/error.rs index bf45a1129ed9c..1bb7cf1253581 100644 --- a/crates/bevy_text/src/error.rs +++ b/crates/bevy_text/src/error.rs @@ -7,6 +7,4 @@ pub enum TextError { NoSuchFont, #[error("failed to add glyph to newly-created atlas {0:?}")] FailedToAddGlyph(GlyphId), - #[error("exceeded {0:?} available TextAltases for font. This can be caused by using an excessive number of font sizes or ui scaling. If you are changing font sizes or ui scaling dynamically consider using Transform::scale to modify the size. If you need more font sizes modify TextSettings.max_font_atlases." )] - ExceedMaxTextAtlases(usize), } diff --git a/crates/bevy_text/src/font_atlas_set.rs b/crates/bevy_text/src/font_atlas_set.rs index 2649e37243391..9468a5405fcfb 100644 --- a/crates/bevy_text/src/font_atlas_set.rs +++ b/crates/bevy_text/src/font_atlas_set.rs @@ -1,4 +1,4 @@ -use crate::{error::TextError, Font, FontAtlas, TextSettings}; +use crate::{error::TextError, Font, FontAtlas}; use ab_glyph::{GlyphId, OutlinedGlyph, Point}; use bevy_asset::{Assets, Handle}; use bevy_math::Vec2; @@ -14,6 +14,8 @@ type FontSizeKey = FloatOrd; #[uuid = "73ba778b-b6b5-4f45-982d-d21b6b86ace2"] pub struct FontAtlasSet { font_atlases: HashMap>, + // TODO unused, remove + #[allow(dead_code)] queue: Vec, } @@ -52,22 +54,7 @@ impl FontAtlasSet { texture_atlases: &mut Assets, textures: &mut Assets, outlined_glyph: OutlinedGlyph, - text_settings: &TextSettings, ) -> Result { - if !text_settings.allow_dynamic_font_size { - if self.font_atlases.len() >= text_settings.max_font_atlases.get() { - return Err(TextError::ExceedMaxTextAtlases( - text_settings.max_font_atlases.get(), - )); - } - } else { - // Clear last space in queue to make room for new font size - while self.queue.len() >= text_settings.max_font_atlases.get() - 1 { - if let Some(font_size_key) = self.queue.pop() { - self.font_atlases.remove(&font_size_key); - } - } - } let glyph = outlined_glyph.glyph(); let glyph_id = glyph.id; let glyph_position = glyph.position; @@ -82,7 +69,7 @@ impl FontAtlasSet { Vec2::splat(512.0), )] }); - self.queue.insert(0, FloatOrd(font_size)); + let glyph_texture = Font::get_outlined_glyph_texture(outlined_glyph); let add_char_to_font_atlas = |atlas: &mut FontAtlas| -> bool { atlas.add_glyph( @@ -129,12 +116,6 @@ impl FontAtlasSet { glyph_id: GlyphId, position: Point, ) -> Option { - // Move to front of used queue. - let some_index = self.queue.iter().position(|x| *x == FloatOrd(font_size)); - if let Some(index) = some_index { - let key = self.queue.remove(index); - self.queue.insert(0, key); - } self.font_atlases .get(&FloatOrd(font_size)) .and_then(|font_atlases| { @@ -151,4 +132,8 @@ impl FontAtlasSet { }) }) } + + pub fn num_font_atlases(&self) -> usize { + self.font_atlases.len() + } } diff --git a/crates/bevy_text/src/glyph_brush.rs b/crates/bevy_text/src/glyph_brush.rs index 476b8064608d0..b3b8e3d79f018 100644 --- a/crates/bevy_text/src/glyph_brush.rs +++ b/crates/bevy_text/src/glyph_brush.rs @@ -3,13 +3,14 @@ use bevy_asset::{Assets, Handle}; use bevy_math::Vec2; use bevy_render::texture::Image; use bevy_sprite::TextureAtlas; +use bevy_utils::tracing::warn; use glyph_brush_layout::{ FontId, GlyphPositioner, Layout, SectionGeometry, SectionGlyph, SectionText, ToSectionText, }; use crate::{ - error::TextError, Font, FontAtlasSet, GlyphAtlasInfo, TextAlignment, TextSettings, - YAxisOrientation, + error::TextError, Font, FontAtlasSet, FontAtlasWarning, GlyphAtlasInfo, TextAlignment, + TextSettings, YAxisOrientation, }; pub struct GlyphBrush { @@ -56,6 +57,7 @@ impl GlyphBrush { texture_atlases: &mut Assets, textures: &mut Assets, text_settings: &TextSettings, + font_atlas_warning: &mut FontAtlasWarning, y_axis_orientation: YAxisOrientation, ) -> Result, TextError> { if glyphs.is_empty() { @@ -114,14 +116,17 @@ impl GlyphBrush { .get_glyph_atlas_info(section_data.2, glyph_id, glyph_position) .map(Ok) .unwrap_or_else(|| { - font_atlas_set.add_glyph_to_atlas( - texture_atlases, - textures, - outlined_glyph, - text_settings, - ) + font_atlas_set.add_glyph_to_atlas(texture_atlases, textures, outlined_glyph) })?; + if !text_settings.allow_dynamic_font_size + && !font_atlas_warning.warned + && font_atlas_set.num_font_atlases() > text_settings.max_font_atlases.get() + { + warn!("warning[B0005]: Number of font atlases has exceeded the maximum of {}. Performance and memory usage may suffer.", text_settings.max_font_atlases.get()); + font_atlas_warning.warned = true; + } + let texture_atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap(); let glyph_rect = texture_atlas.textures[atlas_info.glyph_index]; let size = Vec2::new(glyph_rect.width(), glyph_rect.height()); diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 7c71aa815530e..79f6909289a5e 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -56,6 +56,11 @@ impl Default for TextSettings { } } +#[derive(Resource, Default)] +pub struct FontAtlasWarning { + warned: bool, +} + /// Text is rendered for two different view projections, normal `Text2DBundle` is rendered with a /// `BottomToTop` y axis, and UI is rendered with a `TopToBottom` y axis. This matters for text because /// the glyph positioning is different in either layout. @@ -77,6 +82,7 @@ impl Plugin for TextPlugin { .register_type::() .init_asset_loader::() .init_resource::() + .init_resource::() .insert_resource(TextPipeline::default()) .add_system_to_stage( CoreStage::PostUpdate, diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs index 4f252e8e13dd2..3726f57fd4038 100644 --- a/crates/bevy_text/src/pipeline.rs +++ b/crates/bevy_text/src/pipeline.rs @@ -10,8 +10,8 @@ use bevy_utils::HashMap; use glyph_brush_layout::{FontId, SectionText}; use crate::{ - error::TextError, glyph_brush::GlyphBrush, scale_value, Font, FontAtlasSet, PositionedGlyph, - TextAlignment, TextSection, TextSettings, YAxisOrientation, + error::TextError, glyph_brush::GlyphBrush, scale_value, Font, FontAtlasSet, FontAtlasWarning, + PositionedGlyph, TextAlignment, TextSection, TextSettings, YAxisOrientation, }; #[derive(Default, Resource)] @@ -50,6 +50,7 @@ impl TextPipeline { texture_atlases: &mut Assets, textures: &mut Assets, text_settings: &TextSettings, + font_atlas_warning: &mut FontAtlasWarning, y_axis_orientation: YAxisOrientation, ) -> Result { let mut scaled_fonts = Vec::new(); @@ -106,6 +107,7 @@ impl TextPipeline { texture_atlases, textures, text_settings, + font_atlas_warning, y_axis_orientation, )?; diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index 43cafbffe8923..1143755e1931b 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -22,8 +22,8 @@ use bevy_utils::HashSet; use bevy_window::{WindowId, WindowScaleFactorChanged, Windows}; use crate::{ - Font, FontAtlasSet, HorizontalAlign, Text, TextError, TextLayoutInfo, TextPipeline, - TextSettings, VerticalAlign, YAxisOrientation, + Font, FontAtlasSet, FontAtlasWarning, HorizontalAlign, Text, TextError, TextLayoutInfo, + TextPipeline, TextSettings, VerticalAlign, YAxisOrientation, }; /// The calculated size of text drawn in 2D scene. @@ -159,6 +159,7 @@ pub fn update_text2d_layout( fonts: Res>, windows: Res, text_settings: Res, + mut font_atlas_warning: ResMut, mut scale_factor_changed: EventReader, mut texture_atlases: ResMut>, mut font_atlas_set_storage: ResMut>, @@ -198,6 +199,7 @@ pub fn update_text2d_layout( &mut texture_atlases, &mut textures, text_settings.as_ref(), + &mut font_atlas_warning, YAxisOrientation::BottomToTop, ) { Err(TextError::NoSuchFont) => { @@ -205,8 +207,7 @@ pub fn update_text2d_layout( // queue for further processing queue.insert(entity); } - Err(e @ TextError::FailedToAddGlyph(_)) - | Err(e @ TextError::ExceedMaxTextAtlases(_)) => { + Err(e @ TextError::FailedToAddGlyph(_)) => { panic!("Fatal error when processing text: {e}."); } Ok(info) => { diff --git a/crates/bevy_time/src/timer.rs b/crates/bevy_time/src/timer.rs index 1ed613df9195c..369036eeed6df 100644 --- a/crates/bevy_time/src/timer.rs +++ b/crates/bevy_time/src/timer.rs @@ -155,7 +155,7 @@ impl Timer { self.duration = duration; } - /// Returns `true` if the timer is repeating. + /// Returns the mode of the timer. /// /// # Examples /// ``` @@ -168,7 +168,7 @@ impl Timer { self.mode } - /// Sets whether the timer is repeating or not. + /// Sets the mode of the timer. /// /// # Examples /// ``` @@ -177,6 +177,7 @@ impl Timer { /// timer.set_mode(TimerMode::Once); /// assert_eq!(timer.mode(), TimerMode::Once); /// ``` + #[doc(alias = "repeating")] #[inline] pub fn set_mode(&mut self, mode: TimerMode) { if self.mode != TimerMode::Repeating && mode == TimerMode::Repeating && self.finished { diff --git a/crates/bevy_transform/Cargo.toml b/crates/bevy_transform/Cargo.toml index a39b47c4de85a..e26f887d10264 100644 --- a/crates/bevy_transform/Cargo.toml +++ b/crates/bevy_transform/Cargo.toml @@ -17,5 +17,8 @@ bevy_math = { path = "../bevy_math", version = "0.9.0" } bevy_reflect = { path = "../bevy_reflect", version = "0.9.0", features = ["bevy"] } serde = { version = "1", features = ["derive"], optional = true } +[dev_dependencies] +bevy_tasks = { path = "../bevy_tasks", version = "0.9.0-dev" } + [features] serialize = ["dep:serde", "bevy_math/serialize"] diff --git a/crates/bevy_transform/src/components/global_transform.rs b/crates/bevy_transform/src/components/global_transform.rs index 9abfa1413e825..bbf059cf25dfd 100644 --- a/crates/bevy_transform/src/components/global_transform.rs +++ b/crates/bevy_transform/src/components/global_transform.rs @@ -19,8 +19,8 @@ use bevy_reflect::{std_traits::ReflectDefault, FromReflect, Reflect}; /// /// [`GlobalTransform`] is the position of an entity relative to the reference frame. /// -/// [`GlobalTransform`] is updated from [`Transform`] in the system -/// [`transform_propagate_system`](crate::transform_propagate_system). +/// [`GlobalTransform`] is updated from [`Transform`] in the systems labeled +/// [`TransformPropagate`](crate::TransformSystem::TransformPropagate). /// /// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you /// update the [`Transform`] of an entity in this stage or after, you will notice a 1 frame lag @@ -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/components/transform.rs b/crates/bevy_transform/src/components/transform.rs index 439f3c53a1e05..1a437788ebfb1 100644 --- a/crates/bevy_transform/src/components/transform.rs +++ b/crates/bevy_transform/src/components/transform.rs @@ -20,8 +20,8 @@ use std::ops::Mul; /// /// [`GlobalTransform`] is the position of an entity relative to the reference frame. /// -/// [`GlobalTransform`] is updated from [`Transform`] in the system -/// [`transform_propagate_system`](crate::transform_propagate_system). +/// [`GlobalTransform`] is updated from [`Transform`] in the systems labeled +/// [`TransformPropagate`](crate::TransformSystem::TransformPropagate). /// /// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you /// update the [`Transform`] of an entity in this stage or after, you will notice a 1 frame lag @@ -117,9 +117,8 @@ impl Transform { } } - /// Updates and returns this [`Transform`] by rotating it so that its unit - /// vector in the local negative `Z` direction is toward `target` and its - /// unit vector in the local `Y` direction is toward `up`. + /// Returns this [`Transform`] with a new rotation so that [`Transform::forward`] + /// points towards the `target` position and [`Transform::up`] points towards `up`. #[inline] #[must_use] pub fn looking_at(mut self, target: Vec3, up: Vec3) -> Self { @@ -127,6 +126,15 @@ impl Transform { self } + /// Returns this [`Transform`] with a new rotation so that [`Transform::forward`] + /// points in the given `direction` and [`Transform::up`] points towards `up`. + #[inline] + #[must_use] + pub fn looking_to(mut self, direction: Vec3, up: Vec3) -> Self { + self.look_to(direction, up); + self + } + /// Returns this [`Transform`] with a new translation. #[inline] #[must_use] @@ -314,11 +322,18 @@ impl Transform { self.rotate(rotation); } - /// Rotates this [`Transform`] so that its local negative `Z` direction is toward - /// `target` and its local `Y` direction is toward `up`. + /// Rotates this [`Transform`] so that [`Transform::forward`] points towards the `target` position, + /// and [`Transform::up`] points towards `up`. #[inline] pub fn look_at(&mut self, target: Vec3, up: Vec3) { - let forward = Vec3::normalize(self.translation - target); + self.look_to(target - self.translation, up); + } + + /// Rotates this [`Transform`] so that [`Transform::forward`] points in the given `direction` + /// and [`Transform::up`] points towards `up`. + #[inline] + pub fn look_to(&mut self, direction: Vec3, up: Vec3) { + let forward = -direction.normalize(); let right = up.cross(forward).normalize(); let up = forward.cross(right); self.rotation = Quat::from_mat3(&Mat3::from_cols(right, up, forward)); diff --git a/crates/bevy_transform/src/lib.rs b/crates/bevy_transform/src/lib.rs index e5712b19068bd..0c09757573aeb 100644 --- a/crates/bevy_transform/src/lib.rs +++ b/crates/bevy_transform/src/lib.rs @@ -1,10 +1,10 @@ #![warn(missing_docs)] +#![warn(clippy::undocumented_unsafe_blocks)] #![doc = include_str!("../README.md")] /// The basic components of the transform crate pub mod components; mod systems; -pub use crate::systems::transform_propagate_system; #[doc(hidden)] pub mod prelude { @@ -32,8 +32,8 @@ use prelude::{GlobalTransform, Transform}; /// /// [`GlobalTransform`] is the position of an entity relative to the reference frame. /// -/// [`GlobalTransform`] is updated from [`Transform`] in the system -/// [`transform_propagate_system`]. +/// [`GlobalTransform`] is updated from [`Transform`] in the systems labeled +/// [`TransformPropagate`](crate::TransformSystem::TransformPropagate). /// /// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you /// update the [`Transform`] of an entity in this stage or after, you will notice a 1 frame lag @@ -91,11 +91,19 @@ impl Plugin for TransformPlugin { // add transform systems to startup so the first update is "correct" .add_startup_system_to_stage( StartupStage::PostStartup, - systems::transform_propagate_system.label(TransformSystem::TransformPropagate), + systems::sync_simple_transforms.label(TransformSystem::TransformPropagate), + ) + .add_startup_system_to_stage( + StartupStage::PostStartup, + systems::propagate_transforms.label(TransformSystem::TransformPropagate), + ) + .add_system_to_stage( + CoreStage::PostUpdate, + systems::sync_simple_transforms.label(TransformSystem::TransformPropagate), ) .add_system_to_stage( CoreStage::PostUpdate, - systems::transform_propagate_system.label(TransformSystem::TransformPropagate), + systems::propagate_transforms.label(TransformSystem::TransformPropagate), ); } } diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index f96b56d676447..53de43a51357f 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -2,74 +2,135 @@ use crate::components::{GlobalTransform, Transform}; use bevy_ecs::prelude::{Changed, Entity, Query, With, Without}; use bevy_hierarchy::{Children, Parent}; +/// Update [`GlobalTransform`] component of entities that aren't in the hierarchy +pub fn sync_simple_transforms( + mut query: Query< + (&Transform, &mut GlobalTransform), + (Changed, Without, Without), + >, +) { + query.par_for_each_mut(1024, |(transform, mut global_transform)| { + *global_transform = GlobalTransform::from(*transform); + }); +} + /// Update [`GlobalTransform`] component of entities based on entity hierarchy and /// [`Transform`] component. -pub fn transform_propagate_system( +pub fn propagate_transforms( mut root_query: Query< ( - Option<(&Children, Changed)>, + Entity, + &Children, &Transform, Changed, + Changed, &mut GlobalTransform, - Entity, ), Without, >, - mut transform_query: Query<( - &Transform, - Changed, - &mut GlobalTransform, - &Parent, - )>, + transform_query: Query<(&Transform, Changed, &mut GlobalTransform), With>, + parent_query: Query<&Parent>, children_query: Query<(&Children, Changed), (With, With)>, ) { - for (children, transform, transform_changed, mut global_transform, entity) in - root_query.iter_mut() - { - let mut changed = transform_changed; - if transform_changed { - *global_transform = GlobalTransform::from(*transform); - } + root_query.par_for_each_mut( + // The differing depths and sizes of hierarchy trees causes the work for each root to be + // different. A batch size of 1 ensures that each tree gets it's own task and multiple + // large trees are not clumped together. + 1, + |(entity, children, transform, mut changed, children_changed, mut global_transform)| { + if changed { + *global_transform = GlobalTransform::from(*transform); + } - if let Some((children, changed_children)) = children { // If our `Children` has changed, we need to recalculate everything below us - changed |= changed_children; - for child in children { - let _ = propagate_recursive( - &global_transform, - &mut transform_query, - &children_query, - *child, - entity, - changed, - ); + changed |= children_changed; + + for child in children.iter() { + // 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, - transform_query: &mut Query<( - &Transform, - Changed, - &mut GlobalTransform, - &Parent, - )>, + unsafe_transform_query: &Query< + (&Transform, Changed, &mut GlobalTransform), + With, + >, + parent_query: &Query<&Parent>, children_query: &Query<(&Children, Changed), (With, With)>, - entity: Entity, 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 -) -> Result<(), ()> { +) { + let Ok(actual_parent) = parent_query.get(entity) else { + panic!("Propagated child for {:?} has no Parent component!", entity); + }; + assert_eq!( + actual_parent.get(), expected_parent, + "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle" + ); + let global_matrix = { - let (transform, transform_changed, mut global_transform, child_parent) = - transform_query.get_mut(entity).map_err(drop)?; - // Note that for parallelising, this check cannot occur here, since there is an `&mut GlobalTransform` (in global_transform) - assert_eq!( - child_parent.get(), expected_parent, - "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle" - ); + let Ok((transform, transform_changed, mut global_transform)) = + // SAFETY: This call cannot create aliased mutable references. + // - The top level iteration parallelizes on the roots of the hierarchy. + // - The above assertion ensures that each child has one and only one unique parent throughout the entire + // hierarchy. + // + // For example, consider the following malformed hierarchy: + // + // A + // / \ + // B C + // \ / + // D + // + // D has two parents, B and C. If the propagation passes through C, but the Parent component on D points to B, + // the above check will panic as the origin parent does match the recorded parent. + // + // Also consider the following case, where A and B are roots: + // + // A B + // \ / + // C D + // \ / + // E + // + // Even if these A and B start two separate tasks running in parallel, one of them will panic before attempting + // to mutably access E. + (unsafe { unsafe_transform_query.get_unchecked(entity) }) else { + return; + }; + changed |= transform_changed; if changed { *global_transform = parent.mul_transform(*transform); @@ -77,20 +138,26 @@ fn propagate_recursive( *global_transform }; - let (children, changed_children) = children_query.get(entity).map_err(drop)?; + let Ok((children, changed_children)) = children_query.get(entity) else { + return + }; // If our `Children` has changed, we need to recalculate everything below us changed |= changed_children; for child in children { - let _ = propagate_recursive( - &global_matrix, - transform_query, - children_query, - *child, - entity, - 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, + ); + } } - Ok(()) } #[cfg(test)] @@ -99,9 +166,10 @@ mod test { use bevy_ecs::prelude::*; use bevy_ecs::system::CommandQueue; use bevy_math::vec3; + use bevy_tasks::{ComputeTaskPool, TaskPool}; use crate::components::{GlobalTransform, Transform}; - use crate::systems::transform_propagate_system; + use crate::systems::*; use crate::TransformBundle; use bevy_hierarchy::{BuildChildren, BuildWorldChildren, Children, Parent}; @@ -110,10 +178,12 @@ mod test { #[test] fn did_propagate() { + ComputeTaskPool::init(TaskPool::default); let mut world = World::default(); let mut update_stage = SystemStage::parallel(); - update_stage.add_system(transform_propagate_system); + update_stage.add_system(sync_simple_transforms); + update_stage.add_system(propagate_transforms); let mut schedule = Schedule::default(); schedule.add_stage(Update, update_stage); @@ -152,8 +222,10 @@ mod test { #[test] fn did_propagate_command_buffer() { let mut world = World::default(); + let mut update_stage = SystemStage::parallel(); - update_stage.add_system(transform_propagate_system); + update_stage.add_system(sync_simple_transforms); + update_stage.add_system(propagate_transforms); let mut schedule = Schedule::default(); schedule.add_stage(Update, update_stage); @@ -192,10 +264,12 @@ mod test { #[test] fn correct_children() { + ComputeTaskPool::init(TaskPool::default); let mut world = World::default(); let mut update_stage = SystemStage::parallel(); - update_stage.add_system(transform_propagate_system); + update_stage.add_system(sync_simple_transforms); + update_stage.add_system(propagate_transforms); let mut schedule = Schedule::default(); schedule.add_stage(Update, update_stage); @@ -272,8 +346,10 @@ mod test { #[test] fn correct_transforms_when_no_children() { let mut app = App::new(); + ComputeTaskPool::init(TaskPool::default); - app.add_system(transform_propagate_system); + app.add_system(sync_simple_transforms); + app.add_system(propagate_transforms); let translation = vec3(1.0, 0.0, 0.0); @@ -313,15 +389,14 @@ mod test { #[test] #[should_panic] fn panic_when_hierarchy_cycle() { + ComputeTaskPool::init(TaskPool::default); // We cannot directly edit Parent and Children, so we use a temp world to break // the hierarchy's invariants. let mut temp = World::new(); let mut app = App::new(); - // FIXME: Parallel executors seem to have some odd interaction with the other - // tests in this crate. Using single_threaded until a root cause can be found. - app.add_stage("single", SystemStage::single_threaded()) - .add_system_to_stage("single", transform_propagate_system); + app.add_system(propagate_transforms) + .add_system(sync_simple_transforms); fn setup_world(world: &mut World) -> (Entity, Entity) { let mut grandchild = Entity::from_raw(0); diff --git a/crates/bevy_ui/Cargo.toml b/crates/bevy_ui/Cargo.toml index 8b85a40b088a3..f0724e65d5e97 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 da903e513787e..e653e15b97dbe 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. @@ -30,8 +29,9 @@ impl Default for UiCameraConfig { impl ExtractComponent for UiCameraConfig { type Query = &'static Self; type Filter = With; + type Out = Self; - fn extract_component(item: QueryItem<'_, Self::Query>) -> Self { - item.clone() + fn extract_component(item: QueryItem<'_, Self::Query>) -> Option { + Some(item.clone()) } } diff --git a/crates/bevy_ui/src/flex/convert.rs b/crates/bevy_ui/src/flex/convert.rs index c959aa9dba0b2..c60feb97cd591 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 ebe6e1ad4d394..43715a8cbd4ae 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/focus.rs b/crates/bevy_ui/src/focus.rs index f3e63bf3e2b95..c27c6fe041159 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -1,5 +1,6 @@ use crate::{camera_config::UiCameraConfig, CalculatedClip, Node, UiStack}; use bevy_ecs::{ + change_detection::DetectChanges, entity::Entity, prelude::Component, query::WorldQuery, @@ -179,10 +180,8 @@ pub fn ui_focus_system( Some(*entity) } else { if let Some(mut interaction) = node.interaction { - if *interaction == Interaction::Hovered - || (cursor_position.is_none() && *interaction != Interaction::None) - { - *interaction = Interaction::None; + if *interaction == Interaction::Hovered || (cursor_position.is_none()) { + interaction.set_if_neq(Interaction::None); } } None @@ -227,8 +226,8 @@ pub fn ui_focus_system( while let Some(node) = iter.fetch_next() { if let Some(mut interaction) = node.interaction { // don't reset clicked nodes because they're handled separately - if *interaction != Interaction::Clicked && *interaction != Interaction::None { - *interaction = Interaction::None; + if *interaction != Interaction::Clicked { + interaction.set_if_neq(Interaction::None); } } } diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 8b9e1b2564b81..2ac32e95005ff 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -104,7 +104,6 @@ impl Plugin for UiPlugin { .register_type::() .register_type::() .register_type::() - .register_type::() .add_system_to_stage( CoreStage::PreUpdate, ui_focus_system.label(UiSystem::Focus).after(InputSystem), @@ -127,7 +126,7 @@ impl Plugin for UiPlugin { ) .add_system_to_stage( CoreStage::PostUpdate, - widget::image_node_system + widget::update_image_calculated_size_system .before(UiSystem::Flex) // Potential conflicts: `Assets` // They run independently since `widget::image_node_system` will only ever observe diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index 938a740a48e76..8fd7d49f99cb2 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -1,8 +1,8 @@ //! This module contains basic node bundles used to build UIs use crate::{ - widget::{Button, ImageMode}, - BackgroundColor, CalculatedSize, FocusPolicy, Interaction, Node, Style, UiImage, ZIndex, + widget::Button, BackgroundColor, CalculatedSize, FocusPolicy, Interaction, Node, Style, + UiImage, ZIndex, }; use bevy_ecs::bundle::Bundle; use bevy_render::{ @@ -67,13 +67,11 @@ pub struct ImageBundle { pub node: Node, /// Describes the style including flexbox settings pub style: Style, - /// Configures how the image should scale - pub image_mode: ImageMode, /// The calculated size based on the given image 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, @@ -180,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, @@ -215,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/mod.rs b/crates/bevy_ui/src/render/mod.rs index e7bc5cf6f78cf..595896c1fdfec 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -102,32 +102,24 @@ pub fn build_ui_render(app: &mut App) { draw_ui_graph::node::UI_PASS, RunGraphOnViewNode::new(draw_ui_graph::NAME), ); - graph_2d - .add_node_edge( - bevy_core_pipeline::core_2d::graph::node::MAIN_PASS, - draw_ui_graph::node::UI_PASS, - ) - .unwrap(); - graph_2d - .add_slot_edge( - graph_2d.input_node().unwrap().id, - bevy_core_pipeline::core_2d::graph::input::VIEW_ENTITY, - draw_ui_graph::node::UI_PASS, - RunGraphOnViewNode::IN_VIEW, - ) - .unwrap(); - graph_2d - .add_node_edge( - bevy_core_pipeline::core_2d::graph::node::END_MAIN_PASS_POST_PROCESSING, - draw_ui_graph::node::UI_PASS, - ) - .unwrap(); - graph_2d - .add_node_edge( - draw_ui_graph::node::UI_PASS, - bevy_core_pipeline::core_2d::graph::node::UPSCALING, - ) - .unwrap(); + graph_2d.add_node_edge( + bevy_core_pipeline::core_2d::graph::node::MAIN_PASS, + draw_ui_graph::node::UI_PASS, + ); + graph_2d.add_slot_edge( + graph_2d.input_node().id, + bevy_core_pipeline::core_2d::graph::input::VIEW_ENTITY, + draw_ui_graph::node::UI_PASS, + RunGraphOnViewNode::IN_VIEW, + ); + graph_2d.add_node_edge( + bevy_core_pipeline::core_2d::graph::node::END_MAIN_PASS_POST_PROCESSING, + draw_ui_graph::node::UI_PASS, + ); + graph_2d.add_node_edge( + draw_ui_graph::node::UI_PASS, + bevy_core_pipeline::core_2d::graph::node::UPSCALING, + ); } if let Some(graph_3d) = graph.get_sub_graph_mut(bevy_core_pipeline::core_3d::graph::NAME) { @@ -136,32 +128,24 @@ pub fn build_ui_render(app: &mut App) { draw_ui_graph::node::UI_PASS, RunGraphOnViewNode::new(draw_ui_graph::NAME), ); - graph_3d - .add_node_edge( - bevy_core_pipeline::core_3d::graph::node::MAIN_PASS, - draw_ui_graph::node::UI_PASS, - ) - .unwrap(); - graph_3d - .add_node_edge( - bevy_core_pipeline::core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING, - draw_ui_graph::node::UI_PASS, - ) - .unwrap(); - graph_3d - .add_node_edge( - draw_ui_graph::node::UI_PASS, - bevy_core_pipeline::core_3d::graph::node::UPSCALING, - ) - .unwrap(); - graph_3d - .add_slot_edge( - graph_3d.input_node().unwrap().id, - bevy_core_pipeline::core_3d::graph::input::VIEW_ENTITY, - draw_ui_graph::node::UI_PASS, - RunGraphOnViewNode::IN_VIEW, - ) - .unwrap(); + graph_3d.add_node_edge( + bevy_core_pipeline::core_3d::graph::node::MAIN_PASS, + draw_ui_graph::node::UI_PASS, + ); + graph_3d.add_node_edge( + bevy_core_pipeline::core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING, + draw_ui_graph::node::UI_PASS, + ); + graph_3d.add_node_edge( + draw_ui_graph::node::UI_PASS, + bevy_core_pipeline::core_3d::graph::node::UPSCALING, + ); + graph_3d.add_slot_edge( + graph_3d.input_node().id, + bevy_core_pipeline::core_3d::graph::input::VIEW_ENTITY, + draw_ui_graph::node::UI_PASS, + RunGraphOnViewNode::IN_VIEW, + ); } } @@ -173,14 +157,12 @@ fn get_ui_graph(render_app: &mut App) -> RenderGraph { draw_ui_graph::input::VIEW_ENTITY, SlotType::Entity, )]); - ui_graph - .add_slot_edge( - input_node_id, - draw_ui_graph::input::VIEW_ENTITY, - draw_ui_graph::node::UI_PASS, - UiPassNode::IN_VIEW, - ) - .unwrap(); + ui_graph.add_slot_edge( + input_node_id, + draw_ui_graph::input::VIEW_ENTITY, + draw_ui_graph::node::UI_PASS, + UiPassNode::IN_VIEW, + ); ui_graph } @@ -606,7 +588,7 @@ pub fn queue_uinodes( label: Some("ui_view_bind_group"), layout: &ui_pipeline.view_layout, })); - let draw_ui_function = draw_functions.read().get_id::().unwrap(); + let draw_ui_function = draw_functions.read().id::(); for (view, mut transparent_phase) in &mut views { let pipeline = pipelines.specialize( &mut pipeline_cache, diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 527e96538f1f3..70406c7240bdc 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(()) } } diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 738d578d6e5a3..6b1dd0343029c 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 c6e0e5ad23ed1..fa2e011232cec 100644 --- a/crates/bevy_ui/src/widget/image.rs +++ b/crates/bevy_ui/src/widget/image.rs @@ -1,29 +1,16 @@ use crate::{CalculatedSize, Size, UiImage, Val}; use bevy_asset::Assets; use bevy_ecs::{ - component::Component, - query::{With, Without}, - reflect::ReflectComponent, + query::Without, system::{Query, Res}, }; -use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use bevy_render::texture::Image; use bevy_text::Text; -use serde::{Deserialize, Serialize}; - -/// Describes how to resize the Image node -#[derive(Component, Debug, Default, Clone, Reflect, Serialize, Deserialize)] -#[reflect(Component, Serialize, Deserialize)] -pub enum ImageMode { - /// Keep the aspect ratio of the image - #[default] - KeepAspect, -} /// Updates calculated size of the node based on the image provided -pub fn image_node_system( +pub fn update_image_calculated_size_system( textures: Res>, - mut query: Query<(&mut CalculatedSize, &UiImage), (With, Without)>, + mut query: Query<(&mut CalculatedSize, &UiImage), Without>, ) { for (mut calculated_size, image) in &mut query { if let Some(texture) = textures.get(&image.texture) { @@ -34,6 +21,7 @@ pub fn image_node_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_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 267a00cb56a31..8455a9455af13 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -9,8 +9,8 @@ use bevy_math::Vec2; use bevy_render::texture::Image; use bevy_sprite::TextureAtlas; use bevy_text::{ - Font, FontAtlasSet, Text, TextError, TextLayoutInfo, TextPipeline, TextSettings, - YAxisOrientation, + Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextPipeline, + TextSettings, YAxisOrientation, }; use bevy_window::Windows; @@ -53,6 +53,7 @@ pub fn text_system( fonts: Res>, windows: Res, text_settings: Res, + mut font_atlas_warning: ResMut, ui_scale: Res, mut texture_atlases: ResMut>, mut font_atlas_set_storage: ResMut>, @@ -126,6 +127,7 @@ pub fn text_system( &mut texture_atlases, &mut textures, text_settings.as_ref(), + &mut font_atlas_warning, YAxisOrientation::TopToBottom, ) { Err(TextError::NoSuchFont) => { @@ -133,8 +135,7 @@ pub fn text_system( // queue for further processing new_queue.push(entity); } - Err(e @ TextError::FailedToAddGlyph(_)) - | Err(e @ TextError::ExceedMaxTextAtlases(_)) => { + Err(e @ TextError::FailedToAddGlyph(_)) => { panic!("Fatal error when processing text: {e}."); } Ok(info) => { diff --git a/crates/bevy_utils/src/futures.rs b/crates/bevy_utils/src/futures.rs index 1b752a33c70ff..6e185144021f4 100644 --- a/crates/bevy_utils/src/futures.rs +++ b/crates/bevy_utils/src/futures.rs @@ -1,14 +1,21 @@ +//! Utilities for working with [`Future`]s. +//! +//! [`Future`]: std::future::Future use std::{ future::Future, pin::Pin, task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, }; +/// Consumes the future, polls it once, and immediately returns the output +/// or returns `None` if it wasn't ready yet. +/// +/// This will cancel the future if it's not ready. pub fn now_or_never(mut future: F) -> Option { let noop_waker = noop_waker(); let mut cx = Context::from_waker(&noop_waker); - // Safety: `future` is not moved and the original value is shadowed + // SAFETY: `future` is not moved and the original value is shadowed let future = unsafe { Pin::new_unchecked(&mut future) }; match future.poll(&mut cx) { diff --git a/crates/bevy_utils/src/label.rs b/crates/bevy_utils/src/label.rs index 835656569c1fe..6c504b0283a30 100644 --- a/crates/bevy_utils/src/label.rs +++ b/crates/bevy_utils/src/label.rs @@ -5,9 +5,16 @@ use std::{ hash::{Hash, Hasher}, }; +/// An object safe version of [`Eq`]. This trait is automatically implemented +/// for any `'static` type that implements `Eq`. pub trait DynEq: Any { + /// Casts the type to `dyn Any`. fn as_any(&self) -> &dyn Any; + /// This method tests for `self` and `other` values to be equal. + /// + /// Implementers should avoid returning `true` when the underlying types are + /// not the same. fn dyn_eq(&self, other: &dyn DynEq) -> bool; } @@ -27,9 +34,15 @@ where } } +/// An object safe version of [`Hash`]. This trait is automatically implemented +/// for any `'static` type that implements `Hash`. pub trait DynHash: DynEq { + /// Casts the type to `dyn Any`. fn as_dyn_eq(&self) -> &dyn DynEq; + /// Feeds this value into the given [`Hasher`]. + /// + /// [`Hash`]: std::hash::Hasher fn dyn_hash(&self, state: &mut dyn Hasher); } diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index d942ad4feda4d..840c1fdf927f3 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -1,3 +1,11 @@ +//! General utilities for first-party [Bevy] engine crates. +//! +//! [Bevy]: https://bevyengine.org/ + +#![warn(missing_docs)] +#![warn(clippy::undocumented_unsafe_blocks)] + +#[allow(missing_docs)] pub mod prelude { pub use crate::default; } @@ -30,12 +38,14 @@ use std::{ pin::Pin, }; +/// An owned and dynamically typed Future used when you can’t statically type your result or need to add some indirection. #[cfg(not(target_arch = "wasm32"))] pub type BoxedFuture<'a, T> = Pin + Send + 'a>>; #[cfg(target_arch = "wasm32")] pub type BoxedFuture<'a, T> = Pin + 'a>>; +/// A shortcut alias for [`hashbrown::hash_map::Entry`]. pub type Entry<'a, K, V> = hashbrown::hash_map::Entry<'a, K, V, RandomState>; /// A hasher builder that will create a fixed hasher. @@ -174,6 +184,8 @@ impl BuildHasher for PassHash { } } +/// A no-op hash that only works on `u64`s. Will panic if attempting to +/// hash a type containing non-u64 fields. #[derive(Debug, Default)] pub struct PassHasher { hash: u64, diff --git a/crates/bevy_utils/src/synccell.rs b/crates/bevy_utils/src/synccell.rs index 838915fe2500a..4902cd03c3eb8 100644 --- a/crates/bevy_utils/src/synccell.rs +++ b/crates/bevy_utils/src/synccell.rs @@ -1,3 +1,7 @@ +//! A reimplementation of the currently unstable [`std::sync::Exclusive`] +//! +//! [`std::sync::Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html + /// See [`Exclusive`](https://github.com/rust-lang/rust/issues/98407) for stdlib's upcoming implementation, /// which should replace this one entirely. /// diff --git a/crates/bevy_window/Cargo.toml b/crates/bevy_window/Cargo.toml index 5686739692608..4b4add266b715 100644 --- a/crates/bevy_window/Cargo.toml +++ b/crates/bevy_window/Cargo.toml @@ -17,7 +17,9 @@ serialize = ["serde"] bevy_app = { path = "../bevy_app", version = "0.9.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.9.0" } bevy_math = { path = "../bevy_math", version = "0.9.0" } -bevy_reflect = { path = "../bevy_reflect", version = "0.9.0" } +bevy_reflect = { path = "../bevy_reflect", version = "0.9.0", features = [ + "glam", +] } bevy_utils = { path = "../bevy_utils", version = "0.9.0" } # Used for close_on_esc bevy_input = { path = "../bevy_input", version = "0.9.0" } diff --git a/crates/bevy_window/src/event.rs b/crates/bevy_window/src/event.rs index 5f97b1ac410ce..6c8b0e10ba8f5 100644 --- a/crates/bevy_window/src/event.rs +++ b/crates/bevy_window/src/event.rs @@ -2,10 +2,19 @@ use std::path::PathBuf; use super::{WindowDescriptor, WindowId}; use bevy_math::{IVec2, Vec2}; +use bevy_reflect::{FromReflect, Reflect}; + +#[cfg(feature = "serialize")] +use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; /// A window event that is sent whenever a window's logical size has changed. -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] pub struct WindowResized { pub id: WindowId, /// The new logical width of the window. @@ -15,8 +24,13 @@ pub struct WindowResized { } /// An event that indicates that a new window should be created. -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] pub struct CreateWindow { pub id: WindowId, pub descriptor: WindowDescriptor, @@ -24,16 +38,26 @@ pub struct CreateWindow { /// An event that indicates the window should redraw, even if its control flow is set to `Wait` and /// there have been no window events. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] pub struct RequestRedraw; /// An event that is sent whenever a new window is created. /// /// To create a new window, send a [`CreateWindow`] event - this /// event will be sent in the handler for that event. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] pub struct WindowCreated { pub id: WindowId, } @@ -49,8 +73,13 @@ pub struct WindowCreated { /// [`WindowPlugin`]: crate::WindowPlugin /// [`Window`]: crate::Window /// [closing]: crate::Window::close -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] pub struct WindowCloseRequested { pub id: WindowId, } @@ -59,11 +88,17 @@ pub struct WindowCloseRequested { /// handler for [`Window::close`]. /// /// [`Window::close`]: crate::Window::close -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] pub struct WindowClosed { pub id: WindowId, } + /// An event reporting that the mouse cursor has moved on a window. /// /// The event is sent only if the cursor is over one of the application's windows. @@ -73,8 +108,13 @@ pub struct WindowClosed { /// /// [`WindowEvent::CursorMoved`]: https://docs.rs/winit/latest/winit/event/enum.WindowEvent.html#variant.CursorMoved /// [`MouseMotion`]: bevy_input::mouse::MouseMotion -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] pub struct CursorMoved { /// The identifier of the window the cursor has moved on. pub id: WindowId, @@ -82,53 +122,91 @@ pub struct CursorMoved { /// The position of the cursor, in window coordinates. pub position: Vec2, } + /// An event that is sent whenever the user's cursor enters a window. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] pub struct CursorEntered { pub id: WindowId, } + /// An event that is sent whenever the user's cursor leaves a window. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] pub struct CursorLeft { pub id: WindowId, } /// An event that is sent whenever a window receives a character from the OS or underlying system. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] pub struct ReceivedCharacter { pub id: WindowId, pub char: char, } /// An event that indicates a window has received or lost focus. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] pub struct WindowFocused { pub id: WindowId, pub focused: bool, } /// An event that indicates a window's scale factor has changed. -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] pub struct WindowScaleFactorChanged { pub id: WindowId, pub scale_factor: f64, } + /// An event that indicates a window's OS-reported scale factor has changed. -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] pub struct WindowBackendScaleFactorChanged { pub id: WindowId, pub scale_factor: f64, } /// Events related to files being dragged and dropped on a window. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] pub enum FileDragAndDrop { DroppedFile { id: WindowId, path_buf: PathBuf }, @@ -138,8 +216,13 @@ pub enum FileDragAndDrop { } /// An event that is sent when a window is repositioned in physical pixels. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] pub struct WindowMoved { pub id: WindowId, pub position: IVec2, diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index 6bfd8532d2c1b..1632c06042d66 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -24,6 +24,7 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_ecs::schedule::{IntoSystemDescriptor, SystemLabel}; +use std::path::PathBuf; impl Default for WindowPlugin { fn default() -> Self { @@ -97,6 +98,35 @@ impl Plugin for WindowPlugin { if self.close_when_requested { app.add_system(close_when_requested); } + + // Register event types + app.register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::(); + + // Register window descriptor and related types + app.register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::(); + + // Register `PathBuf` as it's used by `FileDragAndDrop` + app.register_type::(); } } diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index f0603771373cd..17b54b508a59c 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -1,11 +1,18 @@ use bevy_math::{DVec2, IVec2, UVec2, Vec2}; -use bevy_reflect::{FromReflect, Reflect}; +use bevy_reflect::{std_traits::ReflectDefault, FromReflect, Reflect}; use bevy_utils::{tracing::warn, Uuid}; -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Reflect, FromReflect)] -#[reflect_value(PartialEq, Hash)] +#[cfg(feature = "serialize")] +use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; + /// A unique ID for a [`Window`]. -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Reflect, FromReflect)] +#[reflect_value(Debug, PartialEq, Hash, Default)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect_value(Serialize, Deserialize) +)] pub struct WindowId(Uuid); /// Presentation mode for a window. @@ -24,8 +31,13 @@ pub struct WindowId(Uuid); /// The presentation mode may be declared in the [`WindowDescriptor`](WindowDescriptor) using [`WindowDescriptor::present_mode`](WindowDescriptor::present_mode) /// or updated on a [`Window`](Window) using [`set_present_mode`](Window::set_present_mode). #[repr(C)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Reflect, FromReflect)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +#[reflect(Debug, PartialEq, Hash)] #[doc(alias = "vsync")] pub enum PresentMode { /// Chooses FifoRelaxed -> Fifo based on availability. @@ -58,8 +70,13 @@ pub enum PresentMode { /// Specifies how the alpha channel of the textures should be handled during compositing. #[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, FromReflect)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +#[reflect(Debug, PartialEq, Hash)] pub enum CompositeAlphaMode { /// Chooses either `Opaque` or `Inherit` automatically,depending on the /// `alpha_mode` that the current surface can support. @@ -125,8 +142,13 @@ impl Default for WindowId { /// Please note that if the window is resizable, then when the window is /// maximized it may have a size outside of these limits. The functionality /// required to disable maximizing is not yet exposed by winit. -#[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Reflect, FromReflect)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +#[reflect(Debug, PartialEq, Default)] pub struct WindowResizeConstraints { pub min_width: f32, pub min_height: f32, @@ -286,6 +308,7 @@ pub struct Window { cursor_icon: CursorIcon, cursor_visible: bool, cursor_grab_mode: CursorGrabMode, + hittest: bool, physical_cursor_position: Option, raw_handle: Option, focused: bool, @@ -351,6 +374,10 @@ pub enum WindowCommand { SetCursorPosition { position: Vec2, }, + /// Set whether or not mouse events within *this* window are captured, or fall through to the Window below. + SetCursorHitTest { + hittest: bool, + }, /// Set whether or not the window is maximized. SetMaximized { maximized: bool, @@ -380,8 +407,13 @@ pub enum WindowCommand { /// Defines if and how the cursor is grabbed. /// /// Use this enum with [`Window::set_cursor_grab_mode`] to grab the cursor. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect, FromReflect)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +#[reflect(Debug, PartialEq)] pub enum CursorGrabMode { /// The cursor can freely leave the window. None, @@ -392,8 +424,13 @@ pub enum CursorGrabMode { } /// Defines the way a window is displayed. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect, FromReflect)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +#[reflect(Debug, PartialEq)] pub enum WindowMode { /// Creates a window that uses the given size. Windowed, @@ -435,6 +472,7 @@ impl Window { cursor_visible: window_descriptor.cursor_visible, cursor_grab_mode: window_descriptor.cursor_grab_mode, cursor_icon: CursorIcon::Default, + hittest: true, physical_cursor_position: None, raw_handle, focused: false, @@ -705,8 +743,11 @@ impl Window { /// /// ## Platform-specific /// - /// - **`macOS`** doesn't support cursor grab, but most windowing plugins can emulate it. See [issue #4875](https://github.com/bevyengine/bevy/issues/4875#issuecomment-1153977546) for more information. + /// - **`Windows`** doesn't support [`CursorGrabMode::Locked`] + /// - **`macOS`** doesn't support [`CursorGrabMode::Confined`] /// - **`iOS/Android`** don't have cursors. + /// + /// Since `Windows` and `macOS` have different [`CursorGrabMode`] support, it's possible the value returned here is not the same as the one actually sent to winit. #[inline] pub fn cursor_grab_mode(&self) -> CursorGrabMode { self.cursor_grab_mode @@ -717,8 +758,11 @@ impl Window { /// /// ## Platform-specific /// - /// - **`macOS`** doesn't support cursor grab, but most windowing plugins can emulate it. See [issue #4875](https://github.com/bevyengine/bevy/issues/4875#issuecomment-1153977546) for more information. + /// - **`Windows`** doesn't support [`CursorGrabMode::Locked`] + /// - **`macOS`** doesn't support [`CursorGrabMode::Confined`] /// - **`iOS/Android`** don't have cursors. + /// + /// Since `Windows` and `macOS` have different [`CursorGrabMode`] support, we first try to set the grab mode that was asked for. If it doesn't work then use the alternate grab mode. pub fn set_cursor_grab_mode(&mut self, grab_mode: CursorGrabMode) { self.cursor_grab_mode = grab_mode; self.command_queue @@ -777,7 +821,20 @@ impl Window { self.command_queue .push(WindowCommand::SetCursorPosition { position }); } - + /// Modifies whether the window catches cursor events. + /// + /// If true, the window will catch the cursor events. + /// If false, events are passed through the window such that any other window behind it receives them. By default hittest is enabled. + pub fn set_cursor_hittest(&mut self, hittest: bool) { + self.hittest = hittest; + self.command_queue + .push(WindowCommand::SetCursorHitTest { hittest }); + } + /// Get whether or not the hittest is active. + #[inline] + pub fn hittest(&self) -> bool { + self.hittest + } #[allow(missing_docs)] #[inline] pub fn update_focused_status_from_backend(&mut self, focused: bool) { @@ -815,12 +872,12 @@ impl Window { .push(WindowCommand::SetAlwaysOnTop { always_on_top }); } /// Close the operating system window corresponding to this [`Window`]. - /// + /// /// This will also lead to this [`Window`] being removed from the /// [`Windows`] resource. /// /// If the default [`WindowPlugin`] is used, when no windows are - /// open, the [app will exit](bevy_app::AppExit). + /// open, the [app will exit](bevy_app::AppExit). /// To disable this behaviour, set `exit_on_all_closed` on the [`WindowPlugin`] /// to `false` /// @@ -850,7 +907,7 @@ impl Window { /// The "html canvas" element selector. /// /// If set, this selector will be used to find a matching html canvas element, - /// rather than creating a new one. + /// rather than creating a new one. /// Uses the [CSS selector format](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector). /// /// This value has no effect on non-web platforms. @@ -873,8 +930,13 @@ impl Window { } /// Defines where window should be placed at on creation. -#[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Reflect, FromReflect)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +#[reflect(Debug, PartialEq)] pub enum WindowPosition { /// The position will be set by the window manager. Automatic, @@ -891,8 +953,13 @@ pub enum WindowPosition { } /// Defines which monitor to use. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect, FromReflect)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +#[reflect(Debug, PartialEq)] pub enum MonitorSelection { /// Uses current monitor of the window. /// @@ -912,8 +979,13 @@ pub enum MonitorSelection { /// See [`examples/window/window_settings.rs`] for usage. /// /// [`examples/window/window_settings.rs`]: https://github.com/bevyengine/bevy/blob/latest/examples/window/window_settings.rs -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +#[reflect(Debug, PartialEq, Default)] pub struct WindowDescriptor { /// The requested logical width of the window's client area. /// @@ -961,6 +1033,8 @@ pub struct WindowDescriptor { pub cursor_visible: bool, /// Sets whether and how the window grabs the cursor. pub cursor_grab_mode: CursorGrabMode, + /// Sets whether or not the window listens for 'hits' of mouse activity over _this_ window. + pub hittest: bool, /// Sets the [`WindowMode`](crate::WindowMode). /// /// The monitor to go fullscreen on can be selected with the `monitor` field. @@ -975,7 +1049,7 @@ pub struct WindowDescriptor { /// The "html canvas" element selector. /// /// If set, this selector will be used to find a matching html canvas element, - /// rather than creating a new one. + /// rather than creating a new one. /// Uses the [CSS selector format](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector). /// /// This value has no effect on non-web platforms. @@ -1013,6 +1087,7 @@ impl Default for WindowDescriptor { decorations: true, cursor_grab_mode: CursorGrabMode::None, cursor_visible: true, + hittest: true, mode: WindowMode::Windowed, transparent: false, canvas: None, diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 0b1b544eec17b..749d256882500 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -4,7 +4,7 @@ mod web_resize; mod winit_config; mod winit_windows; -use converters::convert_cursor_grab_mode; +use winit::window::CursorGrabMode; pub use winit_config::*; pub use winit_windows::*; @@ -136,9 +136,18 @@ fn change_window( } bevy_window::WindowCommand::SetCursorGrabMode { grab_mode } => { let window = winit_windows.get_window(id).unwrap(); - window - .set_cursor_grab(convert_cursor_grab_mode(grab_mode)) - .unwrap_or_else(|e| error!("Unable to un/grab cursor: {}", e)); + match grab_mode { + bevy_window::CursorGrabMode::None => { + window.set_cursor_grab(CursorGrabMode::None) + } + bevy_window::CursorGrabMode::Confined => window + .set_cursor_grab(CursorGrabMode::Confined) + .or_else(|_e| window.set_cursor_grab(CursorGrabMode::Locked)), + bevy_window::CursorGrabMode::Locked => window + .set_cursor_grab(CursorGrabMode::Locked) + .or_else(|_e| window.set_cursor_grab(CursorGrabMode::Confined)), + } + .unwrap_or_else(|e| error!("Unable to un/grab cursor: {}", e)); } bevy_window::WindowCommand::SetCursorVisibility { visible } => { let window = winit_windows.get_window(id).unwrap(); @@ -230,6 +239,10 @@ fn change_window( let window = winit_windows.get_window(id).unwrap(); window.set_always_on_top(always_on_top); } + bevy_window::WindowCommand::SetCursorHitTest { hittest } => { + let window = winit_windows.get_window(id).unwrap(); + window.set_cursor_hittest(hittest).unwrap(); + } bevy_window::WindowCommand::Close => { // Since we have borrowed `windows` to iterate through them, we can't remove the window from it. // Add the removal requests to a queue to solve this @@ -471,15 +484,7 @@ pub fn winit_runner_with(mut app: App) { } }, WindowEvent::Touch(touch) => { - let mut location = touch.location.to_logical(window.scale_factor()); - - // On a mobile window, the start is from the top while on PC/Linux/OSX from - // bottom - if cfg!(target_os = "android") || cfg!(target_os = "ios") { - let window_height = windows.primary().height(); - location.y = window_height - location.y; - } - + let location = touch.location.to_logical(window.scale_factor()); world.send_event(converters::convert_touch_input(touch, location)); } WindowEvent::ReceivedCharacter(c) => { diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 3364c850b3c28..5967d08d9caa4 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/deny.toml b/deny.toml index edcf778f3ec1d..4bb93186c686a 100644 --- a/deny.toml +++ b/deny.toml @@ -34,14 +34,10 @@ wildcards = "deny" highlight = "all" # Certain crates/versions that will be skipped when doing duplicate detection. skip = [ - { name = "miniz_oxide", version = "0.5" }, # from image v0.24.4 { name = "ndk-sys", version = "0.3" }, # from rodio v0.16.0 { name = "ndk", version = "0.6" }, # from rodio v0.16.0 { name = "raw-window-handle", version = "0.4" }, # from winit v0.27.4 { name = "nix", version = "0.23" }, # from cpal v0.14.1 - { name = "nix", version = "0.24" }, # from cpal v0.14.1 - { name = "bare-metal", version = "0.2" }, # from postcard v1.0.2 - { name = "nb", version = "0.1" }, # from postcard v1.0.2 { name = "rustc_version", version = "0.2" }, # from postcard v1.0.2 { name = "semver", version = "0.9" }, # from postcard v1.0.2 { name = "windows_aarch64_msvc", version = "0.36" }, # from notify v5.0.0 @@ -50,6 +46,7 @@ skip = [ { name = "windows_x86_64_gnu", version = "0.36" }, # from notify v5.0.0 { name = "windows_x86_64_msvc", version = "0.36" }, # from notify v5.0.0 { name = "windows-sys", version = "0.36" }, # from notify v5.0.0 + { name = "windows", version = "0.37" }, # from rodio v0.16.0 { name = "windows_aarch64_msvc", version = "0.37" }, # from rodio v0.16.0 { name = "windows_i686_gnu", version = "0.37" }, # from rodio v0.16.0 { name = "windows_i686_msvc", version = "0.37" }, # from rodio v0.16.0 diff --git a/docs/linux_dependencies.md b/docs/linux_dependencies.md index 541d694d3baf7..ec7b16b78c956 100644 --- a/docs/linux_dependencies.md +++ b/docs/linux_dependencies.md @@ -92,8 +92,8 @@ sudo pacman -S libx11 pkgconf alsa-lib Install `pipewire-alsa` or `pulseaudio-alsa` depending on the sound server you are using. -Note that for Intel GPUs, Vulkan drivers are not installed by default, you must also install -the `vulkan-intel` for bevy to work. +Depending on your graphics card, you may have to install one of the following: +`vulkan-radeon`, `vulkan-intel`, or `mesa-vulkan-drivers` ## Void @@ -101,29 +101,44 @@ the `vulkan-intel` for bevy to work. 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"`. -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`. +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`. + +[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/) @@ -137,6 +152,10 @@ Note that this template does not add Rust to the environment because there are m sudo emerge --ask libX11 pkgconf alsa-lib ``` +When using an AMD Radeon GPU, you may also need to emerge `amdgpu-pro-vulkan` to get Bevy to find the GPU. + +When using a NVIDIA GPU with the proprietary driver (eg. `x11-drivers/nvidia-drivers`), you may also need to emerge `media-libs/vulkan-loader` to get Bevy to find the GPU. NVIDIA Vulkan driver is included in `nvidia-driver`, but may need the loader to find the correct driver. See Gentoo [Documentation](https://wiki.gentoo.org/wiki/Vulkan) for details. + ## [Clear Linux OS](https://clearlinux.org/) ```bash diff --git a/docs/profiling.md b/docs/profiling.md index d7fb745f713ca..67db06825e02a 100644 --- a/docs/profiling.md +++ b/docs/profiling.md @@ -48,6 +48,10 @@ Or you can select an individual system and inspect its statistics (available thr ![A graph and statistics in the Tracy GUI showing the distribution of execution times of an instrumented span in the application](https://user-images.githubusercontent.com/302146/163988464-86e1a3ee-e97b-49ae-9f7e-4ff2b8b761ad.png) +If you save more than one trace, you can compare the spans between both of them by clicking the `Compare` button at the top of the UI. This will open a dialog box asking to load a second trace. From there, it's possible to select any family of spans to more closely compare the timing and distribution of a particular span. + +![A graph and statistics in the Tracy GUI comparing the distribution of execution times of an instrumented span across two traces](https://user-images.githubusercontent.com/3137680/205834698-84405b2f-97b5-43a3-9dba-385167ac1db5.png) + ### Adding your own spans Add spans to your app like this (these are in `bevy::prelude::*` and `bevy::log::*`, just like the normal logging macros). @@ -78,7 +82,7 @@ For more details, check out the [tracing span docs](https://docs.rs/tracing/*/tr This approach requires no extra instrumentation and shows finer-grained flame graphs of actual code call trees. This is useful when you want to identify the specific function of a "hot spot". The downside is that it has higher overhead, so your app will run slower than it normally does. -Install [cargo-flamegraph](https://github.com/killercup/cargo-flamegraph), [enable debug symbols in your release build](https://github.com/killercup/cargo-flamegraph#improving-output-when-running-with---release), then run your app using one of the following commands. Note that `cargo-flamegraph` forwards arguments to cargo. You should treat the `cargo-flamegraph` command as a replacement for `cargo run --release`. The commands below include `--example EXAMPLE_NAME` to illustrate, but you can remove those arguments in favor of whatever you use to run your app: +Install [cargo-flamegraph](https://github.com/flamegraph-rs/flamegraph), [enable debug symbols in your release build](https://github.com/flamegraph-rs/flamegraph#improving-output-when-running-with---release), then run your app using one of the following commands. Note that `cargo-flamegraph` forwards arguments to cargo. You should treat the `cargo-flamegraph` command as a replacement for `cargo run --release`. The commands below include `--example EXAMPLE_NAME` to illustrate, but you can remove those arguments in favor of whatever you use to run your app: * Graph-Like Flame Graph: `RUSTFLAGS='-C force-frame-pointers=y' cargo flamegraph -c "record -g" --example EXAMPLE_NAME` * Flat-ish Flame Graph: `RUSTFLAGS='-C force-frame-pointers=y' cargo flamegraph --example EXAMPLE_NAME` diff --git a/errors/B0005.md b/errors/B0005.md new file mode 100644 index 0000000000000..16b198f5e2cd6 --- /dev/null +++ b/errors/B0005.md @@ -0,0 +1,9 @@ +# B0005 + +A runtime warning. + +Separate font atlases are created for each font and font size. This is expensive, and the memory is never reclaimed when e.g. interpolating `TextStyle::font_size` or `UiScale::scale`. + +If you need to smoothly scale font size, use `Transform::scale`. + +You can disable this warning by setting `TextSettings::allow_dynamic_font_size` to `true` or raise the limit by setting `TextSettings::max_font_atlases`. diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 764701269ef7b..4147a4a4966f4 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 @@ -327,10 +327,7 @@ pub fn queue_colored_mesh2d( } // Iterate each view (a camera is a view) for (visible_entities, mut transparent_phase, view) in &mut views { - let draw_colored_mesh2d = transparent_draw_functions - .read() - .get_id::() - .unwrap(); + let draw_colored_mesh2d = transparent_draw_functions.read().id::(); let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples) | Mesh2dPipelineKey::from_hdr(view.hdr); diff --git a/examples/2d/sprite_sheet.rs b/examples/2d/sprite_sheet.rs index 569821db7c17f..597737bdb3f31 100644 --- a/examples/2d/sprite_sheet.rs +++ b/examples/2d/sprite_sheet.rs @@ -11,23 +11,31 @@ fn main() { .run(); } +#[derive(Component)] +struct AnimationIndices { + first: usize, + last: usize, +} + #[derive(Component, Deref, DerefMut)] struct AnimationTimer(Timer); fn animate_sprite( time: Res