diff --git a/.github/workflows/action-on-PR-labeled.yml b/.github/workflows/action-on-PR-labeled.yml index 6270feeb6b52a..18b8cb7b7f73a 100644 --- a/.github/workflows/action-on-PR-labeled.yml +++ b/.github/workflows/action-on-PR-labeled.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest if: github.event.label.name == 'C-Breaking-Change' && !contains(github.event.pull_request.body, '## Migration Guide') steps: - - uses: actions/github-script@v6 + - uses: actions/github-script@v7 with: script: | await github.rest.issues.createComment({ diff --git a/.github/workflows/ci-comment-failures.yml b/.github/workflows/ci-comment-failures.yml index 44f8a4ac32b26..6ec7f52fb90e1 100644 --- a/.github/workflows/ci-comment-failures.yml +++ b/.github/workflows/ci-comment-failures.yml @@ -23,7 +23,7 @@ jobs: steps: - name: 'Download artifact' id: find-artifact - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: result-encoding: string script: | @@ -50,7 +50,7 @@ jobs: if: ${{ steps.find-artifact.outputs.result == 'true' }} - name: 'Comment on PR' if: ${{ steps.find-artifact.outputs.result == 'true' }} - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -81,7 +81,7 @@ jobs: steps: - name: 'Download artifact' id: find-artifact - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: result-encoding: string script: | @@ -108,7 +108,7 @@ jobs: if: ${{ steps.find-artifact.outputs.result == 'true' }} - name: 'Comment on PR' if: ${{ steps.find-artifact.outputs.result == 'true' }} - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -139,7 +139,7 @@ jobs: steps: - name: 'Download artifact' id: find-artifact - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: result-encoding: string script: | @@ -166,7 +166,7 @@ jobs: if: ${{ steps.find-artifact.outputs.result == 'true' }} - name: 'Comment on PR' if: ${{ steps.find-artifact.outputs.result == 'true' }} - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 440f342269041..7da6ab768c1da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -163,6 +163,26 @@ jobs: VALIDATE_MARKDOWN: true DEFAULT_BRANCH: main + toml: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - name: Install taplo + run: | + curl -fsSL https://github.com/tamasfe/taplo/releases/latest/download/taplo-full-linux-x86_64.gz \ + | gzip -d - \ + | install -m 755 /dev/stdin /usr/local/bin/taplo + - name: Run Taplo + id: taplo + run: taplo fmt --check --diff + - name: Taplo info + if: failure() + run: | + echo 'To fix toml fmt, please run `taplo fmt`' + echo 'Or if you use VSCode, use the Even Better Toml extension' + + run-examples-on-windows-dx12: runs-on: windows-latest timeout-minutes: 60 diff --git a/.github/workflows/welcome.yml b/.github/workflows/welcome.yml index 9e9f72e91e144..c3c67fab53a48 100644 --- a/.github/workflows/welcome.yml +++ b/.github/workflows/welcome.yml @@ -12,7 +12,7 @@ jobs: welcome: runs-on: ubuntu-latest steps: - - uses: actions/github-script@v6 + - uses: actions/github-script@v7 with: script: | // Get a list of all issues created by the PR opener diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 77e190abd1ca4..d2130b9f6853b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -339,9 +339,14 @@ If you're new to Bevy, here's the workflow we use: To locally lint your files using the same workflow as our CI: 1. Install [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli). 2. Run `markdownlint -f -c .github/linters/.markdown-lint.yml .` in the root directory of the Bevy project. -5. Push your changes to your fork on Github and open a Pull Request. -6. Respond to any CI failures or review feedback. While CI failures must be fixed before we can merge your PR, you do not need to *agree* with all feedback from your reviews, merely acknowledge that it was given. If you cannot come to an agreement, leave the thread open and defer to a Maintainer or Project Lead's final judgement. -7. When your PR is ready to merge, a Maintainer or Project Lead will review it and suggest final changes. If those changes are minimal they may even apply them directly to speed up merging. + +5. When working with Toml (`.toml`) files, Bevy's CI will check toml files using [taplo](https://taplo.tamasfe.dev/): `taplo fmt --check --diff` + 1. If you use VSCode, install [Even better toml](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml) and format your files. + 2. If you want to use the cli tool, install [taplo-cli](https://taplo.tamasfe.dev/cli/installation/cargo.html) and run `taplo fmt --check --diff` to check for the formatting. Fix any issues by running `taplo fmt` in the root directory of the Bevy project. + +6. Push your changes to your fork on Github and open a Pull Request. +7. Respond to any CI failures or review feedback. While CI failures must be fixed before we can merge your PR, you do not need to *agree* with all feedback from your reviews, merely acknowledge that it was given. If you cannot come to an agreement, leave the thread open and defer to a Maintainer or Project Lead's final judgement. +8. When your PR is ready to merge, a Maintainer or Project Lead will review it and suggest final changes. If those changes are minimal they may even apply them directly to speed up merging. If you end up adding a new official Bevy crate to the `bevy` repo: diff --git a/Cargo.toml b/Cargo.toml index 70e9a4ca7cb15..bce48e8e4636d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["game", "engine", "gamedev", "graphics", "bevy"] license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/bevyengine/bevy" -rust-version = "1.70.0" +rust-version = "1.74.0" [workspace] exclude = [ @@ -29,6 +29,14 @@ members = [ "errors", ] +[workspace.lints.clippy] +type_complexity = "allow" +doc_markdown = "warn" +undocumented_unsafe_blocks = "warn" + +[lints] +workspace = true + [features] default = [ "animation", @@ -282,6 +290,7 @@ bevy_internal = { path = "crates/bevy_internal", version = "0.12.0", default-fea [dev-dependencies] rand = "0.8.0" ron = "0.8.0" +flate2 = "1.0" serde = { version = "1", features = ["derive"] } bytemuck = "1.7" # Needed to poll Task examples @@ -1077,6 +1086,17 @@ description = "Demonstrates various methods to load assets" category = "Assets" wasm = false +[[example]] +name = "asset_decompression" +path = "examples/asset/asset_decompression.rs" +doc-scrape-examples = true + +[package.metadata.example.asset_decompression] +name = "Asset Decompression" +description = "Demonstrates loading a compressed asset" +category = "Assets" +wasm = false + [[example]] name = "custom_asset" path = "examples/asset/custom_asset.rs" @@ -1109,7 +1129,7 @@ required-features = ["file_watcher"] name = "Hot Reloading of Assets" description = "Demonstrates automatic reloading of assets when modified on disk" category = "Assets" -wasm = true +wasm = false [[example]] name = "asset_processing" @@ -1767,6 +1787,17 @@ description = "A shader and a material that uses it" category = "Shaders" wasm = true +[[example]] +name = "shader_material_2d" +path = "examples/shader/shader_material_2d.rs" +doc-scrape-examples = true + +[package.metadata.example.shader_material_2d] +name = "Material" +description = "A shader and a material that uses it on a 2d mesh" +category = "Shaders" +wasm = true + [[example]] name = "extended_material" path = "examples/shader/extended_material.rs" diff --git a/assets/data/compressed_image.png.gz b/assets/data/compressed_image.png.gz new file mode 100644 index 0000000000000..3d96da393547e Binary files /dev/null and b/assets/data/compressed_image.png.gz differ diff --git a/assets/scenes/load_scene_example.scn.ron b/assets/scenes/load_scene_example.scn.ron index 4c03d1c53cc1a..c1c725c8d2ccd 100644 --- a/assets/scenes/load_scene_example.scn.ron +++ b/assets/scenes/load_scene_example.scn.ron @@ -13,7 +13,12 @@ y: 0.0, z: 0.0 ), - rotation: (0.0, 0.0, 0.0, 1.0), + rotation: ( + x: 0.0, + y: 0.0, + z: 0.0, + w: 1.0, + ), scale: ( x: 1.0, y: 1.0, diff --git a/assets/shaders/custom_material.frag b/assets/shaders/custom_material.frag index 04b644fda876c..004bdf787ae4a 100644 --- a/assets/shaders/custom_material.frag +++ b/assets/shaders/custom_material.frag @@ -3,9 +3,7 @@ layout(location = 0) in vec2 v_Uv; layout(location = 0) out vec4 o_Target; -layout(set = 1, binding = 0) uniform CustomMaterial { - vec4 Color; -}; +layout(set = 1, binding = 0) uniform vec4 CustomMaterial_color; layout(set = 1, binding = 1) uniform texture2D CustomMaterial_texture; layout(set = 1, binding = 2) uniform sampler CustomMaterial_sampler; @@ -16,5 +14,5 @@ layout(set = 1, binding = 2) uniform sampler CustomMaterial_sampler; void main() { // o_Target = PbrFuncs::tone_mapping(Color * texture(sampler2D(CustomMaterial_texture,CustomMaterial_sampler), v_Uv)); - o_Target = Color * texture(sampler2D(CustomMaterial_texture,CustomMaterial_sampler), v_Uv); + o_Target = CustomMaterial_color * texture(sampler2D(CustomMaterial_texture,CustomMaterial_sampler), v_Uv); } diff --git a/assets/shaders/custom_material.wgsl b/assets/shaders/custom_material.wgsl index 90322438e68d2..88d904dd1b87c 100644 --- a/assets/shaders/custom_material.wgsl +++ b/assets/shaders/custom_material.wgsl @@ -2,17 +2,13 @@ // we can import items from shader modules in the assets folder with a quoted path #import "shaders/custom_material_import.wgsl"::COLOR_MULTIPLIER -struct CustomMaterial { - color: vec4, -}; - -@group(1) @binding(0) var material: CustomMaterial; -@group(1) @binding(1) var base_color_texture: texture_2d; -@group(1) @binding(2) var base_color_sampler: sampler; +@group(1) @binding(0) var material_color: vec4; +@group(1) @binding(1) var material_color_texture: texture_2d; +@group(1) @binding(2) var material_color_sampler: sampler; @fragment fn fragment( mesh: VertexOutput, ) -> @location(0) vec4 { - return material.color * textureSample(base_color_texture, base_color_sampler, mesh.uv) * COLOR_MULTIPLIER; + return material_color * textureSample(material_color_texture, material_color_sampler, mesh.uv) * COLOR_MULTIPLIER; } diff --git a/assets/shaders/custom_material_2d.wgsl b/assets/shaders/custom_material_2d.wgsl new file mode 100644 index 0000000000000..98b85afd463b8 --- /dev/null +++ b/assets/shaders/custom_material_2d.wgsl @@ -0,0 +1,12 @@ +#import bevy_sprite::mesh2d_vertex_output::VertexOutput +// we can import items from shader modules in the assets folder with a quoted path +#import "shaders/custom_material_import.wgsl"::COLOR_MULTIPLIER + +@group(1) @binding(0) var material_color: vec4; +@group(1) @binding(1) var base_color_texture: texture_2d; +@group(1) @binding(2) var base_color_sampler: sampler; + +@fragment +fn fragment(mesh: VertexOutput) -> @location(0) vec4 { + return material_color * textureSample(base_color_texture, base_color_sampler, mesh.uv) * COLOR_MULTIPLIER; +} diff --git a/benches/Cargo.toml b/benches/Cargo.toml index f060d83ebfda8..b78352a55dabe 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -61,3 +61,8 @@ harness = false name = "bezier" path = "benches/bevy_math/bezier.rs" harness = false + +[[bench]] +name = "utils" +path = "benches/bevy_utils/entity_hash.rs" +harness = false diff --git a/benches/benches/bevy_utils/entity_hash.rs b/benches/benches/bevy_utils/entity_hash.rs new file mode 100644 index 0000000000000..fa83ee3950d47 --- /dev/null +++ b/benches/benches/bevy_utils/entity_hash.rs @@ -0,0 +1,75 @@ +use bevy_ecs::entity::Entity; +use bevy_utils::EntityHashSet; +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaCha8Rng; + +criterion_group!(benches, entity_set_build_and_lookup,); +criterion_main!(benches); + +const SIZES: [usize; 5] = [100, 316, 1000, 3162, 10000]; + +fn make_entity(rng: &mut impl Rng, size: usize) -> Entity { + // -log₂(1-x) gives an exponential distribution with median 1.0 + // That lets us get values that are mostly small, but some are quite large + // * For ids, half are in [0, size), half are unboundedly larger. + // * For generations, half are in [0, 2), half are unboundedly larger. + + let x: f64 = rng.gen(); + let id = -(1.0 - x).log2() * (size as f64); + let x: f64 = rng.gen(); + let gen = -(1.0 - x).log2() * 2.0; + + // this is not reliable, but we're internal so a hack is ok + let bits = ((gen as u64) << 32) | (id as u64); + let e = Entity::from_bits(bits); + assert_eq!(e.index(), id as u32); + assert_eq!(e.generation(), gen as u32); + e +} + +fn entity_set_build_and_lookup(c: &mut Criterion) { + let mut group = c.benchmark_group("entity_hash"); + for size in SIZES { + // Get some random-but-consistent entities to use for all the benches below. + let mut rng = ChaCha8Rng::seed_from_u64(size as u64); + let entities = Vec::from_iter( + std::iter::repeat_with(|| make_entity(&mut rng, size)).take(size), + ); + + group.throughput(Throughput::Elements(size as u64)); + group.bench_function( + BenchmarkId::new("entity_set_build", size), + |bencher| { + bencher.iter_with_large_drop(|| EntityHashSet::from_iter(entities.iter().copied())); + }, + ); + group.bench_function( + BenchmarkId::new("entity_set_lookup_hit", size), + |bencher| { + let set = EntityHashSet::from_iter(entities.iter().copied()); + bencher.iter(|| entities.iter().copied().filter(|e| set.contains(e)).count()); + }, + ); + group.bench_function( + BenchmarkId::new("entity_set_lookup_miss_id", size), + |bencher| { + let set = EntityHashSet::from_iter(entities.iter().copied()); + bencher.iter(|| entities.iter() + .copied() + .map(|e| Entity::from_bits(e.to_bits() + 1)) + .filter(|e| set.contains(e)).count()); + }, + ); + group.bench_function( + BenchmarkId::new("entity_set_lookup_miss_gen", size), + |bencher| { + let set = EntityHashSet::from_iter(entities.iter().copied()); + bencher.iter(|| entities.iter() + .copied() + .map(|e| Entity::from_bits(e.to_bits() + (1 << 32))) + .filter(|e| set.contains(e)).count()); + }, + ); + } +} diff --git a/crates/bevy_a11y/Cargo.toml b/crates/bevy_a11y/Cargo.toml index f4aed85f15302..d95482ee3b8bc 100644 --- a/crates/bevy_a11y/Cargo.toml +++ b/crates/bevy_a11y/Cargo.toml @@ -15,3 +15,6 @@ bevy_derive = { path = "../bevy_derive", version = "0.12.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" } accesskit = "0.12" + +[lints] +workspace = true diff --git a/crates/bevy_a11y/src/lib.rs b/crates/bevy_a11y/src/lib.rs index 576b920f4183b..4a1948c065ec7 100644 --- a/crates/bevy_a11y/src/lib.rs +++ b/crates/bevy_a11y/src/lib.rs @@ -1,7 +1,6 @@ //! Accessibility for Bevy #![warn(missing_docs)] -#![allow(clippy::type_complexity)] #![forbid(unsafe_code)] use std::sync::{ diff --git a/crates/bevy_animation/Cargo.toml b/crates/bevy_animation/Cargo.toml index 45796ffc66755..f2d5da4cd7dbf 100644 --- a/crates/bevy_animation/Cargo.toml +++ b/crates/bevy_animation/Cargo.toml @@ -14,10 +14,15 @@ bevy_app = { path = "../bevy_app", version = "0.12.0" } bevy_asset = { path = "../bevy_asset", version = "0.12.0" } bevy_core = { path = "../bevy_core", version = "0.12.0" } bevy_math = { path = "../bevy_math", version = "0.12.0" } -bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] } +bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [ + "bevy", +] } bevy_render = { path = "../bevy_render", version = "0.12.0" } bevy_time = { path = "../bevy_time", version = "0.12.0" } bevy_utils = { path = "../bevy_utils", version = "0.12.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" } bevy_transform = { path = "../bevy_transform", version = "0.12.0" } bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" } + +[lints] +workspace = true diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index b1c2ed7c5d26d..e4fdcd360d15a 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -1,7 +1,6 @@ //! Animation for the game engine Bevy #![warn(missing_docs)] -#![allow(clippy::type_complexity)] use std::ops::Deref; use std::time::Duration; @@ -123,7 +122,7 @@ impl AnimationClip { } /// Repetition behavior of an animation. -#[derive(Reflect, Copy, Clone, Default)] +#[derive(Reflect, Debug, PartialEq, Eq, Copy, Clone, Default)] pub enum RepeatAnimation { /// The animation will finish after running once. #[default] @@ -134,7 +133,7 @@ pub enum RepeatAnimation { Forever, } -#[derive(Reflect)] +#[derive(Debug, Reflect)] struct PlayingAnimation { repeat: RepeatAnimation, speed: f32, @@ -190,15 +189,20 @@ impl PlayingAnimation { self.elapsed += delta; self.seek_time += delta * self.speed; - if (self.seek_time > clip_duration && self.speed > 0.0) - || (self.seek_time < 0.0 && self.speed < 0.0) - { + let over_time = self.speed > 0.0 && self.seek_time >= clip_duration; + let under_time = self.speed < 0.0 && self.seek_time < 0.0; + + if over_time || under_time { self.completions += 1; - } + if self.is_finished() { + return; + } + } if self.seek_time >= clip_duration { self.seek_time %= clip_duration; } + // Note: assumes delta is never lower than -clip_duration if self.seek_time < 0.0 { self.seek_time += clip_duration; } @@ -642,6 +646,7 @@ fn apply_animation( let Ok(mut transform) = (unsafe { transforms.get_unchecked(target) }) else { continue; }; + // SAFETY: As above, there can't be other AnimationPlayers with this target so this fetch can't alias let mut morphs = unsafe { morphs.get_unchecked(target) }; for curve in curves { // Some curves have only one keyframe used to set a transform diff --git a/crates/bevy_app/Cargo.toml b/crates/bevy_app/Cargo.toml index 65e920fbbd4ed..88bcfbbba4a90 100644 --- a/crates/bevy_app/Cargo.toml +++ b/crates/bevy_app/Cargo.toml @@ -30,5 +30,7 @@ downcast-rs = "1.2.0" [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = { version = "0.2" } -web-sys = { version = "0.3", features = [ "Window" ] } +web-sys = { version = "0.3", features = ["Window"] } +[lints] +workspace = true diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 5bbbbe424f38a..e70dad72655dd 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -306,14 +306,6 @@ impl App { panic!("App::run() was called from within Plugin::build(), which is not allowed."); } - if app.plugins_state() == PluginsState::Ready { - // If we're already ready, we finish up now and advance one frame. - // This prevents black frames during the launch transition on iOS. - app.finish(); - app.cleanup(); - app.update(); - } - let runner = std::mem::replace(&mut app.runner, Box::new(run_once)); (runner)(app); } @@ -983,20 +975,14 @@ impl App { } fn run_once(mut app: App) { - let plugins_state = app.plugins_state(); - if plugins_state != PluginsState::Cleaned { - while app.plugins_state() == PluginsState::Adding { - #[cfg(not(target_arch = "wasm32"))] - bevy_tasks::tick_global_task_pools_on_main_thread(); - } - app.finish(); - app.cleanup(); + while app.plugins_state() == PluginsState::Adding { + #[cfg(not(target_arch = "wasm32"))] + bevy_tasks::tick_global_task_pools_on_main_thread(); } + app.finish(); + app.cleanup(); - // if plugins where cleaned before the runner start, an update already ran - if plugins_state != PluginsState::Cleaned { - app.update(); - } + app.update(); } /// An event that indicates the [`App`] should exit. This will fully exit the app process at the @@ -1215,4 +1201,36 @@ mod tests { GenericLabel::(PhantomData).intern() ); } + + /// Custom runners should be in charge of when `app::update` gets called as they may need to + /// coordinate some state. + /// bug: + /// fix: + #[test] + fn regression_test_10385() { + use super::{Res, Resource}; + use crate::PreUpdate; + + #[derive(Resource)] + struct MyState {} + + fn my_runner(mut app: App) { + let my_state = MyState {}; + app.world.insert_resource(my_state); + + for _ in 0..5 { + app.update(); + } + } + + fn my_system(_: Res) { + // access state during app update + } + + // Should not panic due to missing resource + App::new() + .set_runner(my_runner) + .add_systems(PreUpdate, my_system) + .run(); + } } diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index 3439620a46633..9aaabc25ee696 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -1,7 +1,6 @@ //! This crate is about everything concerning the highest-level, application layer of a Bevy app. #![warn(missing_docs)] -#![allow(clippy::type_complexity)] mod app; mod main_schedule; diff --git a/crates/bevy_app/src/main_schedule.rs b/crates/bevy_app/src/main_schedule.rs index 0e4fa47d79eed..d85879f476459 100644 --- a/crates/bevy_app/src/main_schedule.rs +++ b/crates/bevy_app/src/main_schedule.rs @@ -71,6 +71,9 @@ pub struct RunFixedUpdateLoop; /// /// The exclusive `run_fixed_update_schedule` system runs this schedule. /// This is run by the [`RunFixedUpdateLoop`] schedule. +/// +/// Frequency of execution is configured by inserting `Time` resource, 64 Hz by default. +/// See [this example](https://github.com/bevyengine/bevy/blob/latest/examples/time/time.rs). #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] pub struct FixedUpdate; diff --git a/crates/bevy_app/src/schedule_runner.rs b/crates/bevy_app/src/schedule_runner.rs index 18b2f0b61fb55..1e17f8322a5ee 100644 --- a/crates/bevy_app/src/schedule_runner.rs +++ b/crates/bevy_app/src/schedule_runner.rs @@ -84,12 +84,7 @@ impl Plugin for ScheduleRunnerPlugin { let mut app_exit_event_reader = ManualEventReader::::default(); match run_mode { - RunMode::Once => { - // if plugins where cleaned before the runner start, an update already ran - if plugins_state != PluginsState::Cleaned { - app.update(); - } - } + RunMode::Once => app.update(), RunMode::Loop { wait } => { let mut tick = move |app: &mut App, wait: Option| diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index 9f659b79c9ad2..c088c61ac4d81 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -38,7 +38,6 @@ parking_lot = { version = "0.12", features = ["arc_lock", "send_guard"] } ron = "0.8" serde = { version = "1", features = ["derive"] } thiserror = "1.0" -notify-debouncer-full = { version = "0.3.1", optional = true } [target.'cfg(target_os = "android")'.dependencies] bevy_winit = { path = "../bevy_winit", version = "0.12.0" } @@ -49,5 +48,11 @@ web-sys = { version = "0.3", features = ["Request", "Window", "Response"] } wasm-bindgen-futures = "0.4" js-sys = "0.3" +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +notify-debouncer-full = { version = "0.3.1", optional = true } + [dev-dependencies] bevy_core = { path = "../bevy_core", version = "0.12.0" } + +[lints] +workspace = true diff --git a/crates/bevy_asset/macros/Cargo.toml b/crates/bevy_asset/macros/Cargo.toml index d1ccd85035277..bf0d46725c39a 100644 --- a/crates/bevy_asset/macros/Cargo.toml +++ b/crates/bevy_asset/macros/Cargo.toml @@ -17,3 +17,6 @@ bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.12.0" } syn = "2.0" proc-macro2 = "1.0" quote = "1.0" + +[lints] +workspace = true diff --git a/crates/bevy_asset/macros/src/lib.rs b/crates/bevy_asset/macros/src/lib.rs index eec95e3c50eb2..6d71b3d2991df 100644 --- a/crates/bevy_asset/macros/src/lib.rs +++ b/crates/bevy_asset/macros/src/lib.rs @@ -1,6 +1,6 @@ use bevy_macro_utils::BevyManifest; use proc_macro::{Span, TokenStream}; -use quote::quote; +use quote::{format_ident, quote}; use syn::{parse_macro_input, Data, DeriveInput, Path}; pub(crate) fn bevy_asset_path() -> syn::Path { @@ -41,33 +41,71 @@ fn derive_dependency_visitor_internal( ast: &DeriveInput, bevy_asset_path: &Path, ) -> Result { - let mut field_visitors = Vec::new(); - if let Data::Struct(data_struct) = &ast.data { - for field in &data_struct.fields { - if field - .attrs - .iter() - .any(|a| a.path().is_ident(DEPENDENCY_ATTRIBUTE)) - { - if let Some(field_ident) = &field.ident { - field_visitors.push(quote! { - #bevy_asset_path::VisitAssetDependencies::visit_dependencies(&self.#field_ident, visit); - }); + let struct_name = &ast.ident; + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + + let visit_dep = |to_read| quote!(#bevy_asset_path::VisitAssetDependencies::visit_dependencies(#to_read, visit);); + let is_dep_attribute = |a: &syn::Attribute| a.path().is_ident(DEPENDENCY_ATTRIBUTE); + let field_has_dep = |f: &syn::Field| f.attrs.iter().any(is_dep_attribute); + + let body = match &ast.data { + Data::Struct(data_struct) => { + let fields = data_struct.fields.iter(); + let field_visitors = fields.enumerate().filter(|(_, f)| field_has_dep(f)); + let field_visitors = field_visitors.map(|(i, field)| match &field.ident { + Some(ident) => visit_dep(quote!(&self.#ident)), + None => { + let index = syn::Index::from(i); + visit_dep(quote!(&self.#index)) } - } + }); + Some(quote!( #(#field_visitors)* )) } - } else { - return Err(syn::Error::new( - Span::call_site().into(), - "Asset derive currently only works on structs", - )); - } + Data::Enum(data_enum) => { + let variant_has_dep = |v: &syn::Variant| v.fields.iter().any(field_has_dep); + let any_case_required = data_enum.variants.iter().any(variant_has_dep); + let cases = data_enum.variants.iter().filter(|v| variant_has_dep(v)); + let cases = cases.map(|variant| { + let ident = &variant.ident; + let fields = &variant.fields; - let struct_name = &ast.ident; - let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + let field_visitors = fields.iter().enumerate().filter(|(_, f)| field_has_dep(f)); + + let field_visitors = field_visitors.map(|(i, field)| match &field.ident { + Some(ident) => visit_dep(quote!(#ident)), + None => { + let ident = format_ident!("member{i}"); + visit_dep(quote!(#ident)) + } + }); + let fields = match fields { + syn::Fields::Named(fields) => { + let named = fields.named.iter().map(|f| f.ident.as_ref()); + quote!({ #(#named,)* .. }) + } + syn::Fields::Unnamed(fields) => { + let named = (0..fields.unnamed.len()).map(|i| format_ident!("member{i}")); + quote!( ( #(#named,)* ) ) + } + syn::Fields::Unit => unreachable!("Can't pass filter is_dep_attribute"), + }; + quote!(Self::#ident #fields => { + #(#field_visitors)* + }) + }); + + any_case_required.then(|| quote!(match self { #(#cases)*, _ => {} })) + } + Data::Union(_) => { + return Err(syn::Error::new( + Span::call_site().into(), + "Asset derive currently doesn't work on unions", + )); + } + }; // prevent unused variable warning in case there are no dependencies - let visit = if field_visitors.is_empty() { + let visit = if body.is_none() { quote! { _visit } } else { quote! { visit } @@ -76,7 +114,7 @@ fn derive_dependency_visitor_internal( Ok(quote! { impl #impl_generics #bevy_asset_path::VisitAssetDependencies for #struct_name #type_generics #where_clause { fn visit_dependencies(&self, #visit: &mut impl FnMut(#bevy_asset_path::UntypedAssetId)) { - #(#field_visitors)* + #body } } }) diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index 5720b7479fe90..ad8951a4aa31e 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -11,6 +11,7 @@ use std::{ hash::{Hash, Hasher}, sync::Arc, }; +use thiserror::Error; /// Provides [`Handle`] and [`UntypedHandle`] _for a specific asset type_. /// This should _only_ be used for one specific asset type. @@ -191,10 +192,7 @@ impl Handle { /// [`Handle::Weak`]. #[inline] pub fn untyped(self) -> UntypedHandle { - match self { - Handle::Strong(handle) => UntypedHandle::Strong(handle), - Handle::Weak(id) => UntypedHandle::Weak(id.untyped()), - } + self.into() } } @@ -224,13 +222,14 @@ impl std::fmt::Debug for Handle { impl Hash for Handle { #[inline] fn hash(&self, state: &mut H) { - Hash::hash(&self.id(), state); + self.id().hash(state); + TypeId::of::().hash(state); } } impl PartialOrd for Handle { fn partial_cmp(&self, other: &Self) -> Option { - Some(self.id().cmp(&other.id())) + Some(self.cmp(other)) } } @@ -249,6 +248,34 @@ impl PartialEq for Handle { impl Eq for Handle {} +impl From> for AssetId { + #[inline] + fn from(value: Handle) -> Self { + value.id() + } +} + +impl From<&Handle> for AssetId { + #[inline] + fn from(value: &Handle) -> Self { + value.id() + } +} + +impl From> for UntypedAssetId { + #[inline] + fn from(value: Handle) -> Self { + value.id().into() + } +} + +impl From<&Handle> for UntypedAssetId { + #[inline] + fn from(value: &Handle) -> Self { + value.id().into() + } +} + /// An untyped variant of [`Handle`], which internally stores the [`Asset`] type information at runtime /// as a [`TypeId`] instead of encoding it in the compile-time type. This allows handles across [`Asset`] types /// to be stored together and compared. @@ -326,12 +353,20 @@ impl UntypedHandle { /// Converts to a typed Handle. This will panic if the internal [`TypeId`] does not match the given asset type `A` #[inline] pub fn typed(self) -> Handle { - assert_eq!( - self.type_id(), - TypeId::of::(), - "The target Handle's TypeId does not match the TypeId of this UntypedHandle" - ); - self.typed_unchecked() + let Ok(handle) = self.try_typed() else { + panic!( + "The target Handle<{}>'s TypeId does not match the TypeId of this UntypedHandle", + std::any::type_name::() + ) + }; + + handle + } + + /// Converts to a typed Handle. This will panic if the internal [`TypeId`] does not match the given asset type `A` + #[inline] + pub fn try_typed(self) -> Result, UntypedAssetConversionError> { + Handle::try_from(self) } /// The "meta transform" for the strong handle. This will only be [`Some`] if the handle is strong and there is a meta transform @@ -383,3 +418,212 @@ impl std::fmt::Debug for UntypedHandle { } } } + +impl PartialOrd for UntypedHandle { + fn partial_cmp(&self, other: &Self) -> Option { + if self.type_id() == other.type_id() { + self.id().partial_cmp(&other.id()) + } else { + None + } + } +} + +impl From for UntypedAssetId { + #[inline] + fn from(value: UntypedHandle) -> Self { + value.id() + } +} + +impl From<&UntypedHandle> for UntypedAssetId { + #[inline] + fn from(value: &UntypedHandle) -> Self { + value.id() + } +} + +// Cross Operations + +impl PartialEq for Handle { + #[inline] + fn eq(&self, other: &UntypedHandle) -> bool { + TypeId::of::() == other.type_id() && self.id() == other.id() + } +} + +impl PartialEq> for UntypedHandle { + #[inline] + fn eq(&self, other: &Handle) -> bool { + other.eq(self) + } +} + +impl PartialOrd for Handle { + #[inline] + fn partial_cmp(&self, other: &UntypedHandle) -> Option { + if TypeId::of::() != other.type_id() { + None + } else { + self.id().partial_cmp(&other.id()) + } + } +} + +impl PartialOrd> for UntypedHandle { + #[inline] + fn partial_cmp(&self, other: &Handle) -> Option { + Some(other.partial_cmp(self)?.reverse()) + } +} + +impl From> for UntypedHandle { + fn from(value: Handle) -> Self { + match value { + Handle::Strong(handle) => UntypedHandle::Strong(handle), + Handle::Weak(id) => UntypedHandle::Weak(id.into()), + } + } +} + +impl TryFrom for Handle { + type Error = UntypedAssetConversionError; + + fn try_from(value: UntypedHandle) -> Result { + let found = value.type_id(); + let expected = TypeId::of::(); + + if found != expected { + return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found }); + } + + match value { + UntypedHandle::Strong(handle) => Ok(Handle::Strong(handle)), + UntypedHandle::Weak(id) => { + let Ok(id) = id.try_into() else { + return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found }); + }; + Ok(Handle::Weak(id)) + } + } + } +} + +/// Errors preventing the conversion of to/from an [`UntypedHandle`] and an [`Handle`]. +#[derive(Error, Debug, PartialEq, Clone)] +#[non_exhaustive] +pub enum UntypedAssetConversionError { + /// Caused when trying to convert an [`UntypedHandle`] into an [`Handle`] of the wrong type. + #[error( + "This UntypedHandle is for {found:?} and cannot be converted into an Handle<{expected:?}>" + )] + TypeIdMismatch { expected: TypeId, found: TypeId }, +} + +#[cfg(test)] +mod tests { + use super::*; + + type TestAsset = (); + + const UUID_1: Uuid = Uuid::from_u128(123); + const UUID_2: Uuid = Uuid::from_u128(456); + + /// Simple utility to directly hash a value using a fixed hasher + fn hash(data: &T) -> u64 { + let mut hasher = bevy_utils::AHasher::default(); + data.hash(&mut hasher); + hasher.finish() + } + + /// Typed and Untyped `Handles` should be equivalent to each other and themselves + #[test] + fn equality() { + let typed = AssetId::::Uuid { uuid: UUID_1 }; + let untyped = UntypedAssetId::Uuid { + type_id: TypeId::of::(), + uuid: UUID_1, + }; + + let typed = Handle::Weak(typed); + let untyped = UntypedHandle::Weak(untyped); + + assert_eq!( + Ok(typed.clone()), + Handle::::try_from(untyped.clone()) + ); + assert_eq!(UntypedHandle::from(typed.clone()), untyped); + assert_eq!(typed, untyped); + } + + /// Typed and Untyped `Handles` should be orderable amongst each other and themselves + #[allow(clippy::cmp_owned)] + #[test] + fn ordering() { + assert!(UUID_1 < UUID_2); + + let typed_1 = AssetId::::Uuid { uuid: UUID_1 }; + let typed_2 = AssetId::::Uuid { uuid: UUID_2 }; + let untyped_1 = UntypedAssetId::Uuid { + type_id: TypeId::of::(), + uuid: UUID_1, + }; + let untyped_2 = UntypedAssetId::Uuid { + type_id: TypeId::of::(), + uuid: UUID_2, + }; + + let typed_1 = Handle::Weak(typed_1); + let typed_2 = Handle::Weak(typed_2); + let untyped_1 = UntypedHandle::Weak(untyped_1); + let untyped_2 = UntypedHandle::Weak(untyped_2); + + assert!(typed_1 < typed_2); + assert!(untyped_1 < untyped_2); + + assert!(UntypedHandle::from(typed_1.clone()) < untyped_2); + assert!(untyped_1 < UntypedHandle::from(typed_2.clone())); + + assert!(Handle::::try_from(untyped_1.clone()).unwrap() < typed_2); + assert!(typed_1 < Handle::::try_from(untyped_2.clone()).unwrap()); + + assert!(typed_1 < untyped_2); + assert!(untyped_1 < typed_2); + } + + /// Typed and Untyped `Handles` should be equivalently hashable to each other and themselves + #[test] + fn hashing() { + let typed = AssetId::::Uuid { uuid: UUID_1 }; + let untyped = UntypedAssetId::Uuid { + type_id: TypeId::of::(), + uuid: UUID_1, + }; + + let typed = Handle::Weak(typed); + let untyped = UntypedHandle::Weak(untyped); + + assert_eq!( + hash(&typed), + hash(&Handle::::try_from(untyped.clone()).unwrap()) + ); + assert_eq!(hash(&UntypedHandle::from(typed.clone())), hash(&untyped)); + assert_eq!(hash(&typed), hash(&untyped)); + } + + /// Typed and Untyped `Handles` should be interchangeable + #[test] + fn conversion() { + let typed = AssetId::::Uuid { uuid: UUID_1 }; + let untyped = UntypedAssetId::Uuid { + type_id: TypeId::of::(), + uuid: UUID_1, + }; + + let typed = Handle::Weak(typed); + let untyped = UntypedHandle::Weak(untyped); + + assert_eq!(typed, Handle::try_from(untyped.clone()).unwrap()); + assert_eq!(UntypedHandle::from(typed.clone()), untyped); + } +} diff --git a/crates/bevy_asset/src/id.rs b/crates/bevy_asset/src/id.rs index 428b992ca0742..a75ae3858e07c 100644 --- a/crates/bevy_asset/src/id.rs +++ b/crates/bevy_asset/src/id.rs @@ -1,4 +1,4 @@ -use crate::{Asset, AssetIndex, Handle, UntypedHandle}; +use crate::{Asset, AssetIndex}; use bevy_reflect::{Reflect, Uuid}; use std::{ any::TypeId, @@ -6,11 +6,12 @@ use std::{ hash::Hash, marker::PhantomData, }; +use thiserror::Error; /// A unique runtime-only identifier for an [`Asset`]. This is cheap to [`Copy`]/[`Clone`] and is not directly tied to the /// lifetime of the Asset. This means it _can_ point to an [`Asset`] that no longer exists. /// -/// For an identifier tied to the lifetime of an asset, see [`Handle`]. +/// For an identifier tied to the lifetime of an asset, see [`Handle`](`crate::Handle`). /// /// For an "untyped" / "generic-less" id, see [`UntypedAssetId`]. #[derive(Reflect)] @@ -53,16 +54,7 @@ impl AssetId { /// _inside_ the [`UntypedAssetId`]. #[inline] pub fn untyped(self) -> UntypedAssetId { - match self { - AssetId::Index { index, .. } => UntypedAssetId::Index { - index, - type_id: TypeId::of::(), - }, - AssetId::Uuid { uuid } => UntypedAssetId::Uuid { - uuid, - type_id: TypeId::of::(), - }, - } + self.into() } #[inline] @@ -95,6 +87,7 @@ impl Display for AssetId { Debug::fmt(self, f) } } + impl Debug for AssetId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -123,6 +116,7 @@ impl Hash for AssetId { #[inline] fn hash(&self, state: &mut H) { self.internal().hash(state); + TypeId::of::().hash(state); } } @@ -164,52 +158,10 @@ impl From for AssetId { } } -impl From> for AssetId { - #[inline] - fn from(value: Handle) -> Self { - value.id() - } -} - -impl From<&Handle> for AssetId { - #[inline] - fn from(value: &Handle) -> Self { - value.id() - } -} - -impl From for AssetId { - #[inline] - fn from(value: UntypedHandle) -> Self { - value.id().typed() - } -} - -impl From<&UntypedHandle> for AssetId { - #[inline] - fn from(value: &UntypedHandle) -> Self { - value.id().typed() - } -} - -impl From for AssetId { - #[inline] - fn from(value: UntypedAssetId) -> Self { - value.typed() - } -} - -impl From<&UntypedAssetId> for AssetId { - #[inline] - fn from(value: &UntypedAssetId) -> Self { - value.typed() - } -} - /// An "untyped" / "generic-less" [`Asset`] identifier that behaves much like [`AssetId`], but stores the [`Asset`] type /// information at runtime instead of compile-time. This increases the size of the type, but it enables storing asset ids /// across asset types together and enables comparisons between them. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Copy, Clone)] pub enum UntypedAssetId { /// A small / efficient runtime identifier that can be used to efficiently look up an asset stored in [`Assets`]. This is /// the "default" identifier used for assets. The alternative(s) (ex: [`UntypedAssetId::Uuid`]) will only be used if assets are @@ -263,13 +215,20 @@ impl UntypedAssetId { /// Panics if the [`TypeId`] of `A` does not match the stored type id. #[inline] pub fn typed(self) -> AssetId { - assert_eq!( - self.type_id(), - TypeId::of::(), - "The target AssetId<{}>'s TypeId does not match the TypeId of this UntypedAssetId", - std::any::type_name::() - ); - self.typed_unchecked() + let Ok(id) = self.try_typed() else { + panic!( + "The target AssetId<{}>'s TypeId does not match the TypeId of this UntypedAssetId", + std::any::type_name::() + ) + }; + + id + } + + /// Try to convert this to a "typed" [`AssetId`]. + #[inline] + pub fn try_typed(self) -> Result, UntypedAssetIdConversionError> { + AssetId::try_from(self) } /// Returns the stored [`TypeId`] of the referenced [`Asset`]. @@ -309,24 +268,30 @@ impl Display for UntypedAssetId { } } -impl From> for UntypedAssetId { +impl PartialEq for UntypedAssetId { #[inline] - fn from(value: AssetId) -> Self { - value.untyped() + fn eq(&self, other: &Self) -> bool { + self.internal().eq(&other.internal()) } } -impl From> for UntypedAssetId { +impl Eq for UntypedAssetId {} + +impl Hash for UntypedAssetId { #[inline] - fn from(value: Handle) -> Self { - value.id().untyped() + fn hash(&self, state: &mut H) { + self.internal().hash(state); + self.type_id().hash(state); } } -impl From<&Handle> for UntypedAssetId { - #[inline] - fn from(value: &Handle) -> Self { - value.id().untyped() +impl PartialOrd for UntypedAssetId { + fn partial_cmp(&self, other: &Self) -> Option { + if self.type_id() != other.type_id() { + None + } else { + Some(self.internal().cmp(&other.internal())) + } } } @@ -375,3 +340,171 @@ impl From for InternalAssetId { Self::Uuid(value) } } + +// Cross Operations + +impl PartialEq for AssetId { + #[inline] + fn eq(&self, other: &UntypedAssetId) -> bool { + TypeId::of::() == other.type_id() && self.internal().eq(&other.internal()) + } +} + +impl PartialEq> for UntypedAssetId { + #[inline] + fn eq(&self, other: &AssetId) -> bool { + other.eq(self) + } +} + +impl PartialOrd for AssetId { + #[inline] + fn partial_cmp(&self, other: &UntypedAssetId) -> Option { + if TypeId::of::() != other.type_id() { + None + } else { + Some(self.internal().cmp(&other.internal())) + } + } +} + +impl PartialOrd> for UntypedAssetId { + #[inline] + fn partial_cmp(&self, other: &AssetId) -> Option { + Some(other.partial_cmp(self)?.reverse()) + } +} + +impl From> for UntypedAssetId { + #[inline] + fn from(value: AssetId) -> Self { + let type_id = TypeId::of::(); + + match value { + AssetId::Index { index, .. } => UntypedAssetId::Index { type_id, index }, + AssetId::Uuid { uuid } => UntypedAssetId::Uuid { type_id, uuid }, + } + } +} + +impl TryFrom for AssetId { + type Error = UntypedAssetIdConversionError; + + #[inline] + fn try_from(value: UntypedAssetId) -> Result { + let found = value.type_id(); + let expected = TypeId::of::(); + + match value { + UntypedAssetId::Index { index, type_id } if type_id == expected => Ok(AssetId::Index { + index, + marker: PhantomData, + }), + UntypedAssetId::Uuid { uuid, type_id } if type_id == expected => { + Ok(AssetId::Uuid { uuid }) + } + _ => Err(UntypedAssetIdConversionError::TypeIdMismatch { expected, found }), + } + } +} + +/// Errors preventing the conversion of to/from an [`UntypedAssetId`] and an [`AssetId`]. +#[derive(Error, Debug, PartialEq, Clone)] +#[non_exhaustive] +pub enum UntypedAssetIdConversionError { + /// Caused when trying to convert an [`UntypedAssetId`] into an [`AssetId`] of the wrong type. + #[error("This UntypedAssetId is for {found:?} and cannot be converted into an AssetId<{expected:?}>")] + TypeIdMismatch { expected: TypeId, found: TypeId }, +} + +#[cfg(test)] +mod tests { + use super::*; + + type TestAsset = (); + + const UUID_1: Uuid = Uuid::from_u128(123); + const UUID_2: Uuid = Uuid::from_u128(456); + + /// Simple utility to directly hash a value using a fixed hasher + fn hash(data: &T) -> u64 { + use std::hash::Hasher; + + let mut hasher = bevy_utils::AHasher::default(); + data.hash(&mut hasher); + hasher.finish() + } + + /// Typed and Untyped `AssetIds` should be equivalent to each other and themselves + #[test] + fn equality() { + let typed = AssetId::::Uuid { uuid: UUID_1 }; + let untyped = UntypedAssetId::Uuid { + type_id: TypeId::of::(), + uuid: UUID_1, + }; + + assert_eq!(Ok(typed), AssetId::try_from(untyped)); + assert_eq!(UntypedAssetId::from(typed), untyped); + assert_eq!(typed, untyped); + } + + /// Typed and Untyped `AssetIds` should be orderable amongst each other and themselves + #[test] + fn ordering() { + assert!(UUID_1 < UUID_2); + + let typed_1 = AssetId::::Uuid { uuid: UUID_1 }; + let typed_2 = AssetId::::Uuid { uuid: UUID_2 }; + let untyped_1 = UntypedAssetId::Uuid { + type_id: TypeId::of::(), + uuid: UUID_1, + }; + let untyped_2 = UntypedAssetId::Uuid { + type_id: TypeId::of::(), + uuid: UUID_2, + }; + + assert!(typed_1 < typed_2); + assert!(untyped_1 < untyped_2); + + assert!(UntypedAssetId::from(typed_1) < untyped_2); + assert!(untyped_1 < UntypedAssetId::from(typed_2)); + + assert!(AssetId::try_from(untyped_1).unwrap() < typed_2); + assert!(typed_1 < AssetId::try_from(untyped_2).unwrap()); + + assert!(typed_1 < untyped_2); + assert!(untyped_1 < typed_2); + } + + /// Typed and Untyped `AssetIds` should be equivalently hashable to each other and themselves + #[test] + fn hashing() { + let typed = AssetId::::Uuid { uuid: UUID_1 }; + let untyped = UntypedAssetId::Uuid { + type_id: TypeId::of::(), + uuid: UUID_1, + }; + + assert_eq!( + hash(&typed), + hash(&AssetId::::try_from(untyped).unwrap()) + ); + assert_eq!(hash(&UntypedAssetId::from(typed)), hash(&untyped)); + assert_eq!(hash(&typed), hash(&untyped)); + } + + /// Typed and Untyped `AssetIds` should be interchangeable + #[test] + fn conversion() { + let typed = AssetId::::Uuid { uuid: UUID_1 }; + let untyped = UntypedAssetId::Uuid { + type_id: TypeId::of::(), + uuid: UUID_1, + }; + + assert_eq!(Ok(typed), AssetId::try_from(untyped)); + assert_eq!(UntypedAssetId::from(typed), untyped); + } +} diff --git a/crates/bevy_asset/src/io/embedded/mod.rs b/crates/bevy_asset/src/io/embedded/mod.rs index e5470cd3d5c3f..f6620b3e69cec 100644 --- a/crates/bevy_asset/src/io/embedded/mod.rs +++ b/crates/bevy_asset/src/io/embedded/mod.rs @@ -131,13 +131,15 @@ macro_rules! embedded_path { /// For example, consider the following file structure in the theoretical `bevy_rock` crate, which provides a Bevy [`Plugin`](bevy_app::Plugin) /// that renders fancy rocks for scenes. /// -/// * `bevy_rock` -/// * `src` -/// * `render` -/// * `rock.wgsl` -/// * `mod.rs` -/// * `lib.rs` -/// * `Cargo.toml` +/// ```text +/// bevy_rock +/// ├── src +/// │   ├── render +/// │  │ ├── rock.wgsl +/// │  │ └── mod.rs +/// │   └── lib.rs +/// └── Cargo.toml +/// ``` /// /// `rock.wgsl` is a WGSL shader asset that the `bevy_rock` plugin author wants to bundle with their crate. They invoke the following /// in `bevy_rock/src/render/mod.rs`: @@ -156,7 +158,7 @@ macro_rules! embedded_path { /// ``` /// /// Some things to note in the path: -/// 1. The non-default `embedded:://` [`AssetSource`] +/// 1. The non-default `embedded://` [`AssetSource`] /// 2. `src` is trimmed from the path /// /// The default behavior also works for cargo workspaces. Pretend the `bevy_rock` crate now exists in a larger workspace in diff --git a/crates/bevy_asset/src/io/file/mod.rs b/crates/bevy_asset/src/io/file/mod.rs index aae016df3bf1d..63a28cbb96a69 100644 --- a/crates/bevy_asset/src/io/file/mod.rs +++ b/crates/bevy_asset/src/io/file/mod.rs @@ -6,6 +6,7 @@ mod file_asset; #[cfg(not(feature = "multi-threaded"))] mod sync_file_asset; +use bevy_log::warn; #[cfg(feature = "file_watcher")] pub use file_watcher::*; @@ -44,12 +45,12 @@ impl FileAssetReader { /// See `get_base_path` below. pub fn new>(path: P) -> Self { let root_path = Self::get_base_path().join(path.as_ref()); - std::fs::create_dir_all(&root_path).unwrap_or_else(|e| { - panic!( + if let Err(e) = std::fs::create_dir_all(&root_path) { + warn!( "Failed to create root directory {:?} for file asset reader: {:?}", root_path, e - ) - }); + ); + } Self { root_path } } diff --git a/crates/bevy_asset/src/io/mod.rs b/crates/bevy_asset/src/io/mod.rs index 14e52cddcb597..a8e185d91caa6 100644 --- a/crates/bevy_asset/src/io/mod.rs +++ b/crates/bevy_asset/src/io/mod.rs @@ -1,3 +1,10 @@ +#[cfg(all(feature = "file_watcher", target_arch = "wasm32"))] +compile_error!( + "The \"file_watcher\" feature for hot reloading does not work \ + on WASM.\nDisable \"file_watcher\" \ + when compiling to WASM" +); + #[cfg(target_os = "android")] pub mod android; pub mod embedded; diff --git a/crates/bevy_asset/src/io/wasm.rs b/crates/bevy_asset/src/io/wasm.rs index 99ff39799b087..2636419299f81 100644 --- a/crates/bevy_asset/src/io/wasm.rs +++ b/crates/bevy_asset/src/io/wasm.rs @@ -77,7 +77,7 @@ impl AssetReader for HttpWasmAssetReader { path: &'a Path, ) -> BoxedFuture<'a, Result>, AssetReaderError>> { Box::pin(async move { - let meta_path = get_meta_path(path); + let meta_path = get_meta_path(&self.root_path.join(path)); Ok(self.fetch_bytes(meta_path).await?) }) } diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 9f9cd44c77b0d..5bfccf6a16f7c 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -1,5 +1,3 @@ -#![allow(clippy::type_complexity)] - pub mod io; pub mod meta; pub mod processor; @@ -37,6 +35,9 @@ pub use server::*; pub use bevy_utils::BoxedFuture; +/// Rusty Object Notation, a crate used to serialize and deserialize bevy assets. +pub use ron; + use crate::{ io::{embedded::EmbeddedAssetRegistry, AssetSourceBuilder, AssetSourceBuilders, AssetSourceId}, processor::{AssetProcessor, Process}, @@ -51,6 +52,13 @@ use bevy_log::error; use bevy_reflect::{FromReflect, GetTypeRegistration, Reflect, TypePath}; use std::{any::TypeId, sync::Arc}; +#[cfg(all(feature = "file_watcher", not(feature = "multi-threaded")))] +compile_error!( + "The \"file_watcher\" feature for hot reloading requires the \ + \"multi-threaded\" feature to be functional.\n\ + Consider either disabling the \"file_watcher\" feature or enabling \"multi-threaded\"" +); + /// Provides "asset" loading and processing functionality. An [`Asset`] is a "runtime value" that is loaded from an [`AssetSource`], /// which can be something like a filesystem, a network, etc. /// @@ -74,6 +82,7 @@ pub struct AssetPlugin { pub mode: AssetMode, } +#[derive(Debug)] pub enum AssetMode { /// Loads assets from their [`AssetSource`]'s default [`AssetReader`] without any "preprocessing". /// @@ -1180,4 +1189,35 @@ mod tests { // running schedule does not error on ambiguity between the 2 uses_assets systems app.world.run_schedule(Update); } + + // validate the Asset derive macro for various asset types + #[derive(Asset, TypePath)] + pub struct TestAsset; + + #[allow(dead_code)] + #[derive(Asset, TypePath)] + pub enum EnumTestAsset { + Unnamed(#[dependency] Handle), + Named { + #[dependency] + handle: Handle, + #[dependency] + vec_handles: Vec>, + #[dependency] + embedded: TestAsset, + }, + StructStyle(#[dependency] TestAsset), + Empty, + } + + #[derive(Asset, TypePath)] + pub struct StructTestAsset { + #[dependency] + handle: Handle, + #[dependency] + embedded: TestAsset, + } + + #[derive(Asset, TypePath)] + pub struct TupleTestAsset(#[dependency] Handle); } diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 3a5bbc7d032aa..008312f200b70 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -5,8 +5,8 @@ use crate::{ Settings, }, path::AssetPath, - Asset, AssetLoadError, AssetServer, AssetServerMode, Assets, Handle, UntypedAssetId, - UntypedHandle, + Asset, AssetLoadError, AssetServer, AssetServerMode, Assets, Handle, LoadedUntypedAsset, + UntypedAssetId, UntypedHandle, }; use bevy_ecs::world::World; use bevy_utils::{BoxedFuture, CowArc, HashMap, HashSet}; @@ -16,7 +16,7 @@ use ron::error::SpannedError; use serde::{Deserialize, Serialize}; use std::{ any::{Any, TypeId}, - path::Path, + path::{Path, PathBuf}, }; use thiserror::Error; @@ -28,7 +28,7 @@ pub trait AssetLoader: Send + Sync + 'static { /// The settings type used by this [`AssetLoader`]. type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>; /// The type of [error](`std::error::Error`) which could be encountered by this loader. - type Error: std::error::Error + Send + Sync + 'static; + type Error: Into>; /// Asynchronously loads [`AssetLoader::Asset`] (and any other labeled assets) from the bytes provided by [`Reader`]. fn load<'a>( &'a self, @@ -90,7 +90,9 @@ where .expect("Loader settings should exist") .downcast_ref::() .expect("AssetLoader settings should match the loader type"); - let asset = ::load(self, reader, settings, &mut load_context).await?; + let asset = ::load(self, reader, settings, &mut load_context) + .await + .map_err(|error| error.into())?; Ok(load_context.finish(asset, Some(meta)).into()) }) } @@ -438,7 +440,13 @@ impl<'a> LoadContext<'a> { Default::default() }; let mut bytes = Vec::new(); - reader.read_to_end(&mut bytes).await?; + reader + .read_to_end(&mut bytes) + .await + .map_err(|source| ReadAssetBytesError::Io { + path: path.path().to_path_buf(), + source, + })?; self.loader_dependencies.insert(path.clone_owned(), hash); Ok(bytes) } @@ -460,6 +468,21 @@ impl<'a> LoadContext<'a> { handle } + /// Retrieves a handle for the asset at the given path and adds that path as a dependency of the asset without knowing its type. + pub fn load_untyped<'b>( + &mut self, + path: impl Into>, + ) -> Handle { + let path = path.into().to_owned(); + let handle = if self.should_load_dependencies { + self.asset_server.load_untyped(path) + } else { + self.asset_server.get_or_create_path_handle(path, None) + }; + self.dependencies.insert(handle.id().untyped()); + handle + } + /// Loads the [`Asset`] of type `A` at the given `path` with the given [`AssetLoader::Settings`] settings `S`. This is a "deferred" /// load. If the settings type `S` does not match the settings expected by `A`'s asset loader, an error will be printed to the log /// and the asset load will fail. @@ -539,6 +562,62 @@ impl<'a> LoadContext<'a> { self.loader_dependencies.insert(path, hash); Ok(loaded_asset) } + + /// Loads the asset at the given `path` directly from the provided `reader`. This is an async function that will wait until the asset is fully loaded before + /// returning. Use this if you need the _value_ of another asset in order to load the current asset, and that value comes from your [`Reader`]. + /// For example, if you are deriving a new asset from the referenced asset, or you are building a collection of assets. This will add the `path` as a + /// "load dependency". + /// + /// If the current loader is used in a [`Process`] "asset preprocessor", such as a [`LoadAndSave`] preprocessor, + /// changing a "load dependency" will result in re-processing of the asset. + /// + /// [`Process`]: crate::processor::Process + /// [`LoadAndSave`]: crate::processor::LoadAndSave + pub async fn load_direct_with_reader<'b>( + &mut self, + reader: &mut Reader<'_>, + path: impl Into>, + ) -> Result { + let path = path.into().into_owned(); + + let loader = self + .asset_server + .get_path_asset_loader(&path) + .await + .map_err(|error| LoadDirectError { + dependency: path.clone(), + error: error.into(), + })?; + + let meta = loader.default_meta(); + + let loaded_asset = self + .asset_server + .load_with_meta_loader_and_reader( + &path, + meta, + &*loader, + reader, + false, + self.populate_hashes, + ) + .await + .map_err(|error| LoadDirectError { + dependency: path.clone(), + error, + })?; + + let info = loaded_asset + .meta + .as_ref() + .and_then(|m| m.processed_info().as_ref()); + + let hash = info.map(|i| i.full_hash).unwrap_or_default(); + + self.loader_dependencies.insert(path, hash); + + Ok(loaded_asset) + } } /// An error produced when calling [`LoadContext::read_asset_bytes`] @@ -553,8 +632,12 @@ pub enum ReadAssetBytesError { #[error(transparent)] MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError), /// Encountered an I/O error while loading an asset. - #[error("Encountered an io error while loading asset: {0}")] - Io(#[from] std::io::Error), + #[error("Encountered an io error while loading asset at `{path}`: {source}")] + Io { + path: PathBuf, + #[source] + source: std::io::Error, + }, #[error("The LoadContext for this read_asset_bytes call requires hash metadata, but it was not provided. This is likely an internal implementation error.")] MissingAssetHash, } diff --git a/crates/bevy_asset/src/path.rs b/crates/bevy_asset/src/path.rs index fccc0783f6249..db2c48fb88b0d 100644 --- a/crates/bevy_asset/src/path.rs +++ b/crates/bevy_asset/src/path.rs @@ -115,7 +115,7 @@ impl<'a> AssetPath<'a> { /// /// This will return a [`ParseAssetPathError`] if `asset_path` is in an invalid format. pub fn try_parse(asset_path: &'a str) -> Result, ParseAssetPathError> { - let (source, path, label) = Self::parse_internal(asset_path).unwrap(); + let (source, path, label) = Self::parse_internal(asset_path)?; Ok(Self { source: match source { Some(source) => AssetSourceId::Name(CowArc::Borrowed(source)), diff --git a/crates/bevy_asset/src/processor/process.rs b/crates/bevy_asset/src/processor/process.rs index ef6a3fbb2f5c5..fd4f46630b387 100644 --- a/crates/bevy_asset/src/processor/process.rs +++ b/crates/bevy_asset/src/processor/process.rs @@ -138,7 +138,7 @@ impl> Process .saver .save(writer, saved_asset, &settings.saver_settings) .await - .map_err(|error| ProcessError::AssetSaveError(Box::new(error)))?; + .map_err(|error| ProcessError::AssetSaveError(error.into()))?; Ok(output_settings) }) } diff --git a/crates/bevy_asset/src/saver.rs b/crates/bevy_asset/src/saver.rs index 0b01b7d91e550..be1b17704bab6 100644 --- a/crates/bevy_asset/src/saver.rs +++ b/crates/bevy_asset/src/saver.rs @@ -14,7 +14,7 @@ pub trait AssetSaver: Send + Sync + 'static { /// The type of [`AssetLoader`] used to load this [`Asset`] type OutputLoader: AssetLoader; /// The type of [error](`std::error::Error`) which could be encountered by this saver. - type Error: std::error::Error + Send + Sync + 'static; + type Error: Into>; /// Saves the given runtime [`Asset`] by writing it to a byte format using `writer`. The passed in `settings` can influence how the /// `asset` is saved. @@ -53,7 +53,9 @@ impl ErasedAssetSaver for S { .downcast_ref::() .expect("AssetLoader settings should match the loader type"); let saved_asset = SavedAsset::::from_loaded(asset).unwrap(); - self.save(writer, saved_asset, settings).await?; + if let Err(err) = self.save(writer, saved_asset, settings).await { + return Err(err.into()); + } Ok(()) }) } diff --git a/crates/bevy_asset/src/server/info.rs b/crates/bevy_asset/src/server/info.rs index b81c3feb07f70..2786a7d4b86b3 100644 --- a/crates/bevy_asset/src/server/info.rs +++ b/crates/bevy_asset/src/server/info.rs @@ -104,6 +104,7 @@ impl AssetInfos { ), type_name, ) + .unwrap() } #[allow(clippy::too_many_arguments)] @@ -116,7 +117,7 @@ impl AssetInfos { path: Option>, meta_transform: Option, loading: bool, - ) -> Result { + ) -> Result { let provider = handle_providers .get(&type_id) .ok_or(MissingHandleProviderError(type_id))?; @@ -151,11 +152,13 @@ impl AssetInfos { ) -> (Handle, bool) { let result = self.get_or_create_path_handle_internal( path, - TypeId::of::(), + Some(TypeId::of::()), loading_mode, meta_transform, ); - let (handle, should_load) = unwrap_with_context(result, std::any::type_name::()); + // it is ok to unwrap because TypeId was specified above + let (handle, should_load) = + unwrap_with_context(result, std::any::type_name::()).unwrap(); (handle.typed_unchecked(), should_load) } @@ -167,20 +170,25 @@ impl AssetInfos { loading_mode: HandleLoadingMode, meta_transform: Option, ) -> (UntypedHandle, bool) { - let result = - self.get_or_create_path_handle_internal(path, type_id, loading_mode, meta_transform); - unwrap_with_context(result, type_name) + let result = self.get_or_create_path_handle_internal( + path, + Some(type_id), + loading_mode, + meta_transform, + ); + // it is ok to unwrap because TypeId was specified above + unwrap_with_context(result, type_name).unwrap() } /// Retrieves asset tracking data, or creates it if it doesn't exist. /// Returns true if an asset load should be kicked off - pub fn get_or_create_path_handle_internal( + pub(crate) fn get_or_create_path_handle_internal( &mut self, path: AssetPath<'static>, - type_id: TypeId, + type_id: Option, loading_mode: HandleLoadingMode, meta_transform: Option, - ) -> Result<(UntypedHandle, bool), MissingHandleProviderError> { + ) -> Result<(UntypedHandle, bool), GetOrCreateHandleInternalError> { match self.path_to_id.entry(path.clone()) { Entry::Occupied(entry) => { let id = *entry.get(); @@ -211,6 +219,9 @@ impl AssetInfos { // We must create a new strong handle for the existing id and ensure that the drop of the old // strong handle doesn't remove the asset from the Assets collection info.handle_drops_to_skip += 1; + let type_id = type_id.ok_or( + GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified, + )?; let provider = self .handle_providers .get(&type_id) @@ -227,6 +238,8 @@ impl AssetInfos { HandleLoadingMode::NotLoading => false, HandleLoadingMode::Request | HandleLoadingMode::Force => true, }; + let type_id = type_id + .ok_or(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified)?; let handle = Self::create_handle_internal( &mut self.infos, &self.handle_providers, @@ -624,13 +637,23 @@ pub(crate) enum HandleLoadingMode { #[error("Cannot allocate a handle because no handle provider exists for asset type {0:?}")] pub struct MissingHandleProviderError(TypeId); -fn unwrap_with_context( - result: Result, +/// An error encountered during [`AssetInfos::get_or_create_path_handle_internal`]. +#[derive(Error, Debug)] +pub(crate) enum GetOrCreateHandleInternalError { + #[error(transparent)] + MissingHandleProviderError(#[from] MissingHandleProviderError), + #[error("Handle does not exist but TypeId was not specified.")] + HandleMissingButTypeIdNotSpecified, +} + +pub(crate) fn unwrap_with_context( + result: Result, type_name: &'static str, -) -> T { +) -> Option { match result { - Ok(value) => value, - Err(_) => { + Ok(value) => Some(value), + Err(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified) => None, + Err(GetOrCreateHandleInternalError::MissingHandleProviderError(_)) => { panic!("Cannot allocate an Asset Handle of type '{type_name}' because the asset type has not been initialized. \ Make sure you have called app.init_asset::<{type_name}>()") } diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index 864faf70fc71b..9ec46439e81e6 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -280,8 +280,11 @@ impl AssetServer { handle } + /// Asynchronously load an asset that you do not know the type of statically. If you _do_ know the type of the asset, + /// you should use [`AssetServer::load`]. If you don't know the type of the asset, but you can't use an async method, + /// consider using [`AssetServer::load_untyped`]. #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"] - pub(crate) async fn load_untyped_async<'a>( + pub async fn load_untyped_async<'a>( &self, path: impl Into>, ) -> Result { @@ -346,7 +349,10 @@ impl AssetServer { ) .into(), }), - Err(_) => server.send_asset_event(InternalAssetEvent::Failed { id }), + Err(err) => { + error!("{err}"); + server.send_asset_event(InternalAssetEvent::Failed { id }); + } } }) .detach(); @@ -379,35 +385,53 @@ impl AssetServer { e })?; - let (handle, should_load) = match input_handle { + // This contains Some(UntypedHandle), if it was retrievable + // If it is None, that is because it was _not_ retrievable, due to + // 1. The handle was not already passed in for this path, meaning we can't just use that + // 2. The asset has not been loaded yet, meaning there is no existing Handle for it + // 3. The path has a label, meaning the AssetLoader's root asset type is not the path's asset type + // + // In the None case, the only course of action is to wait for the asset to load so we can allocate the + // handle for that type. + // + // TODO: Note that in the None case, multiple asset loads for the same path can happen at the same time + // (rather than "early out-ing" in the "normal" case) + // This would be resolved by a universal asset id, as we would not need to resolve the asset type + // to generate the ID. See this issue: https://github.com/bevyengine/bevy/issues/10549 + let handle_result = match input_handle { Some(handle) => { // if a handle was passed in, the "should load" check was already done - (handle, true) + Some((handle, true)) } None => { let mut infos = self.data.infos.write(); - infos.get_or_create_path_handle_untyped( + let result = infos.get_or_create_path_handle_internal( path.clone(), - loader.asset_type_id(), - loader.asset_type_name(), + path.label().is_none().then(|| loader.asset_type_id()), HandleLoadingMode::Request, meta_transform, - ) + ); + unwrap_with_context(result, loader.asset_type_name()) } }; - if path.label().is_none() && handle.type_id() != loader.asset_type_id() { - return Err(AssetLoadError::RequestedHandleTypeMismatch { - path: path.into_owned(), - requested: handle.type_id(), - actual_asset_name: loader.asset_type_name(), - loader_name: loader.type_name(), - }); - } - - if !should_load && !force { - return Ok(handle); - } + let handle = if let Some((handle, should_load)) = handle_result { + if path.label().is_none() && handle.type_id() != loader.asset_type_id() { + return Err(AssetLoadError::RequestedHandleTypeMismatch { + path: path.into_owned(), + requested: handle.type_id(), + actual_asset_name: loader.asset_type_name(), + loader_name: loader.type_name(), + }); + } + if !should_load && !force { + return Ok(handle); + } + Some(handle) + } else { + None + }; + // if the handle result is None, we definitely need to load the asset let (base_handle, base_path) = if path.label().is_some() { let mut infos = self.data.infos.write(); @@ -421,7 +445,7 @@ impl AssetServer { ); (base_handle, base_path) } else { - (handle.clone(), path.clone()) + (handle.clone().unwrap(), path.clone()) }; if let Some(meta_transform) = base_handle.meta_transform() { @@ -433,25 +457,33 @@ impl AssetServer { .await { Ok(mut loaded_asset) => { - if let Some(label) = path.label_cow() { - if !loaded_asset.labeled_assets.contains_key(&label) { - return Err(AssetLoadError::MissingLabel { - base_path, - label: label.to_string(), - }); + let final_handle = if let Some(label) = path.label_cow() { + match loaded_asset.labeled_assets.get(&label) { + Some(labeled_asset) => labeled_asset.handle.clone(), + None => { + return Err(AssetLoadError::MissingLabel { + base_path, + label: label.to_string(), + }); + } } - } + } else { + // if the path does not have a label, the handle must exist at this point + handle.unwrap() + }; + for (_, labeled_asset) in loaded_asset.labeled_assets.drain() { self.send_asset_event(InternalAssetEvent::Loaded { id: labeled_asset.handle.id(), loaded_asset: labeled_asset.asset, }); } + self.send_asset_event(InternalAssetEvent::Loaded { id: base_handle.id(), loaded_asset, }); - Ok(handle) + Ok(final_handle) } Err(err) => { self.send_asset_event(InternalAssetEvent::Failed { @@ -623,7 +655,10 @@ impl AssetServer { ) .into(), }), - Err(_) => server.send_asset_event(InternalAssetEvent::Failed { id }), + Err(err) => { + error!("Failed to load folder. {err}"); + server.send_asset_event(InternalAssetEvent::Failed { id }); + }, } }) .detach(); diff --git a/crates/bevy_audio/Cargo.toml b/crates/bevy_audio/Cargo.toml index 652defe88d17d..ea1e22b283ce9 100644 --- a/crates/bevy_audio/Cargo.toml +++ b/crates/bevy_audio/Cargo.toml @@ -14,7 +14,9 @@ bevy_app = { path = "../bevy_app", version = "0.12.0" } bevy_asset = { path = "../bevy_asset", version = "0.12.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" } bevy_math = { path = "../bevy_math", version = "0.12.0" } -bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] } +bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [ + "bevy", +] } bevy_transform = { path = "../bevy_transform", version = "0.12.0" } bevy_derive = { path = "../bevy_derive", version = "0.12.0" } bevy_utils = { path = "../bevy_utils", version = "0.12.0" } @@ -26,8 +28,9 @@ rodio = { version = "0.17", default-features = false } oboe = { version = "0.5", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] -rodio = { version = "0.17", default-features = false, features = ["wasm-bindgen"] } - +rodio = { version = "0.17", default-features = false, features = [ + "wasm-bindgen", +] } [features] mp3 = ["rodio/mp3"] @@ -43,3 +46,6 @@ symphonia-vorbis = ["rodio/symphonia-vorbis"] symphonia-wav = ["rodio/symphonia-wav"] # Enable using a shared stdlib for cxx on Android. android_shared_stdcxx = ["oboe/shared-stdcxx"] + +[lints] +workspace = true diff --git a/crates/bevy_audio/src/audio.rs b/crates/bevy_audio/src/audio.rs index 80159eb7ef7d2..57a0eebb4c9a8 100644 --- a/crates/bevy_audio/src/audio.rs +++ b/crates/bevy_audio/src/audio.rs @@ -50,6 +50,9 @@ impl VolumeLevel { pub fn get(&self) -> f32 { self.0 } + + /// Zero (silent) volume level + pub const ZERO: Self = VolumeLevel(0.0); } /// The way Bevy manages the sound playback. diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index 1a595ad1c7dd8..5c08a079ce437 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -21,7 +21,6 @@ //! ``` #![forbid(unsafe_code)] -#![allow(clippy::type_complexity)] #![warn(missing_docs)] mod audio; diff --git a/crates/bevy_core/Cargo.toml b/crates/bevy_core/Cargo.toml index 231b9bdcf5f7c..fd46c35c40542 100644 --- a/crates/bevy_core/Cargo.toml +++ b/crates/bevy_core/Cargo.toml @@ -11,10 +11,16 @@ keywords = ["bevy"] [dependencies] # bevy -bevy_app = { path = "../bevy_app", version = "0.12.0", features = ["bevy_reflect"] } -bevy_ecs = { path = "../bevy_ecs", version = "0.12.0", features = ["bevy_reflect"] } +bevy_app = { path = "../bevy_app", version = "0.12.0", features = [ + "bevy_reflect", +] } +bevy_ecs = { path = "../bevy_ecs", version = "0.12.0", features = [ + "bevy_reflect", +] } bevy_math = { path = "../bevy_math", version = "0.12.0" } -bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] } +bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [ + "bevy", +] } bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" } bevy_utils = { path = "../bevy_utils", version = "0.12.0" } @@ -27,3 +33,6 @@ serialize = ["dep:serde"] [dev-dependencies] crossbeam-channel = "0.5.0" + +[lints] +workspace = true diff --git a/crates/bevy_core/src/lib.rs b/crates/bevy_core/src/lib.rs index c222af6a9d9fe..c925bc99362da 100644 --- a/crates/bevy_core/src/lib.rs +++ b/crates/bevy_core/src/lib.rs @@ -1,5 +1,5 @@ #![warn(missing_docs)] -#![allow(clippy::type_complexity)] + //! This crate provides core functionality for Bevy Engine. mod name; diff --git a/crates/bevy_core_pipeline/Cargo.toml b/crates/bevy_core_pipeline/Cargo.toml index e2bdf17000e18..cab1cb55e96a7 100644 --- a/crates/bevy_core_pipeline/Cargo.toml +++ b/crates/bevy_core_pipeline/Cargo.toml @@ -3,8 +3,8 @@ name = "bevy_core_pipeline" version = "0.12.0" edition = "2021" authors = [ - "Bevy Contributors ", - "Carter Anderson ", + "Bevy Contributors ", + "Carter Anderson ", ] description = "Provides a core render pipeline for Bevy Engine." homepage = "https://bevyengine.org" @@ -34,3 +34,6 @@ bevy_utils = { path = "../bevy_utils", version = "0.12.0" } serde = { version = "1", features = ["derive"] } bitflags = "2.3" radsort = "0.1" + +[lints] +workspace = true diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index ec119ad5ae551..f710b8c704650 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -1,5 +1,3 @@ -#![allow(clippy::type_complexity)] - pub mod blit; pub mod bloom; pub mod clear_color; diff --git a/crates/bevy_derive/Cargo.toml b/crates/bevy_derive/Cargo.toml index 9e51c0e5ca046..f8d081f029849 100644 --- a/crates/bevy_derive/Cargo.toml +++ b/crates/bevy_derive/Cargo.toml @@ -16,3 +16,6 @@ bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.12.0" } quote = "1.0" syn = { version = "2.0", features = ["full"] } + +[lints] +workspace = true diff --git a/crates/bevy_derive/src/lib.rs b/crates/bevy_derive/src/lib.rs index b6766c0dc8959..18d61262ee0fd 100644 --- a/crates/bevy_derive/src/lib.rs +++ b/crates/bevy_derive/src/lib.rs @@ -1,5 +1,3 @@ -#![allow(clippy::type_complexity)] - extern crate proc_macro; mod app_plugin; diff --git a/crates/bevy_diagnostic/Cargo.toml b/crates/bevy_diagnostic/Cargo.toml index 18e3d0e27c1cc..dec3647ce9b6c 100644 --- a/crates/bevy_diagnostic/Cargo.toml +++ b/crates/bevy_diagnostic/Cargo.toml @@ -25,9 +25,12 @@ bevy_utils = { path = "../bevy_utils", version = "0.12.0" } [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.29.0", default-features = false, features = [ - "apple-app-store", + "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.29.0", default-features = false } + +[lints] +workspace = true diff --git a/crates/bevy_diagnostic/src/lib.rs b/crates/bevy_diagnostic/src/lib.rs index 0422df0cb4380..aaa0ea449ab34 100644 --- a/crates/bevy_diagnostic/src/lib.rs +++ b/crates/bevy_diagnostic/src/lib.rs @@ -1,5 +1,3 @@ -#![allow(clippy::type_complexity)] - mod diagnostic; mod entity_count_diagnostics_plugin; mod frame_time_diagnostics_plugin; diff --git a/crates/bevy_dylib/Cargo.toml b/crates/bevy_dylib/Cargo.toml index e08f5609838f0..f85950952eb11 100644 --- a/crates/bevy_dylib/Cargo.toml +++ b/crates/bevy_dylib/Cargo.toml @@ -13,3 +13,6 @@ crate-type = ["dylib"] [dependencies] bevy_internal = { path = "../bevy_internal", version = "0.12.0", default-features = false } + +[lints] +workspace = true diff --git a/crates/bevy_dylib/src/lib.rs b/crates/bevy_dylib/src/lib.rs index 2c5d6429676b5..a950f985d611a 100644 --- a/crates/bevy_dylib/src/lib.rs +++ b/crates/bevy_dylib/src/lib.rs @@ -1,5 +1,4 @@ #![warn(missing_docs)] -#![allow(clippy::type_complexity)] #![allow(clippy::single_component_path_imports)] //! Forces dynamic linking of Bevy. diff --git a/crates/bevy_dynamic_plugin/Cargo.toml b/crates/bevy_dynamic_plugin/Cargo.toml index 38164324a0e35..f21424ea47381 100644 --- a/crates/bevy_dynamic_plugin/Cargo.toml +++ b/crates/bevy_dynamic_plugin/Cargo.toml @@ -15,3 +15,6 @@ bevy_app = { path = "../bevy_app", version = "0.12.0" } # other libloading = { version = "0.8" } thiserror = "1.0" + +[lints] +workspace = true diff --git a/crates/bevy_dynamic_plugin/src/lib.rs b/crates/bevy_dynamic_plugin/src/lib.rs index ee59ff2cb135a..4ff6f92606665 100644 --- a/crates/bevy_dynamic_plugin/src/lib.rs +++ b/crates/bevy_dynamic_plugin/src/lib.rs @@ -1,5 +1,3 @@ -#![allow(clippy::type_complexity)] - mod loader; pub use loader::*; diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 82d5736118cc3..3231e7b4101ff 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -44,3 +44,6 @@ path = "examples/resources.rs" [[example]] name = "change_detection" path = "examples/change_detection.rs" + +[lints] +workspace = true diff --git a/crates/bevy_ecs/macros/Cargo.toml b/crates/bevy_ecs/macros/Cargo.toml index d19d740497c84..035a29ff825b4 100644 --- a/crates/bevy_ecs/macros/Cargo.toml +++ b/crates/bevy_ecs/macros/Cargo.toml @@ -14,3 +14,6 @@ bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.12.0" } syn = "2.0" quote = "1.0" proc-macro2 = "1.0" + +[lints] +workspace = true diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 7ee660f9987d7..ecb3a57c9c477 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -5,7 +5,7 @@ mod fetch; mod states; use crate::fetch::derive_world_query_impl; -use bevy_macro_utils::{derive_label, ensure_no_collision, get_named_struct_fields, BevyManifest}; +use bevy_macro_utils::{derive_label, ensure_no_collision, get_struct_fields, BevyManifest}; use proc_macro::TokenStream; use proc_macro2::Span; use quote::{format_ident, quote}; @@ -27,8 +27,8 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); let ecs_path = bevy_ecs_path(); - let named_fields = match get_named_struct_fields(&ast.data) { - Ok(fields) => &fields.named, + let named_fields = match get_struct_fields(&ast.data) { + Ok(fields) => fields, Err(e) => return e.into_compile_error().into(), }; @@ -59,8 +59,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { let field = named_fields .iter() - .map(|field| field.ident.as_ref().unwrap()) + .map(|field| field.ident.as_ref()) .collect::>(); + let field_type = named_fields .iter() .map(|field| &field.ty) @@ -69,20 +70,36 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { let mut field_component_ids = Vec::new(); let mut field_get_components = Vec::new(); let mut field_from_components = Vec::new(); - for ((field_type, field_kind), field) in - field_type.iter().zip(field_kind.iter()).zip(field.iter()) + for (((i, field_type), field_kind), field) in field_type + .iter() + .enumerate() + .zip(field_kind.iter()) + .zip(field.iter()) { match field_kind { BundleFieldKind::Component => { field_component_ids.push(quote! { <#field_type as #ecs_path::bundle::Bundle>::component_ids(components, storages, &mut *ids); }); - field_get_components.push(quote! { - self.#field.get_components(&mut *func); - }); - field_from_components.push(quote! { - #field: <#field_type as #ecs_path::bundle::Bundle>::from_components(ctx, &mut *func), - }); + match field { + Some(field) => { + field_get_components.push(quote! { + self.#field.get_components(&mut *func); + }); + field_from_components.push(quote! { + #field: <#field_type as #ecs_path::bundle::Bundle>::from_components(ctx, &mut *func), + }); + } + None => { + let index = syn::Index::from(i); + field_get_components.push(quote! { + self.#index.get_components(&mut *func); + }); + field_from_components.push(quote! { + #index: <#field_type as #ecs_path::bundle::Bundle>::from_components(ctx, &mut *func), + }); + } + } } BundleFieldKind::Ignore => { @@ -115,7 +132,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { where __F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_> { - Self { + Self{ #(#field_from_components)* } } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 79f3098146432..361689e5793ad 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -833,8 +833,8 @@ impl Bundles { T::component_ids(components, storages, &mut |id| component_ids.push(id)); let id = BundleId(bundle_infos.len()); let bundle_info = - // SAFETY: T::component_id ensures its: - // - info was created + // SAFETY: T::component_id ensures its: + // - info was created // - appropriate storage for it has been initialized. // - was created in the same order as the components in T unsafe { BundleInfo::new(std::any::type_name::(), components, component_ids, id) }; diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 677477680f10c..0723204d70dd0 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -115,12 +115,64 @@ type IdCursor = isize; /// [`EntityCommands`]: crate::system::EntityCommands /// [`Query::get`]: crate::system::Query::get /// [`World`]: crate::world::World -#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Copy)] +// Alignment repr necessary to allow LLVM to better output +// optimised codegen for `to_bits`, `PartialEq` and `Ord`. +#[repr(C, align(8))] pub struct Entity { + // Do not reorder the fields here. The ordering is explicitly used by repr(C) + // to make this struct equivalent to a u64. + #[cfg(target_endian = "little")] + index: u32, generation: u32, + #[cfg(target_endian = "big")] index: u32, } +// By not short-circuiting in comparisons, we get better codegen. +// See +impl PartialEq for Entity { + #[inline] + fn eq(&self, other: &Entity) -> bool { + // By using `to_bits`, the codegen can be optimised out even + // further potentially. Relies on the correct alignment/field + // order of `Entity`. + self.to_bits() == other.to_bits() + } +} + +impl Eq for Entity {} + +// The derive macro codegen output is not optimal and can't be optimised as well +// by the compiler. This impl resolves the issue of non-optimal codegen by relying +// on comparing against the bit representation of `Entity` instead of comparing +// the fields. The result is then LLVM is able to optimise the codegen for Entity +// far beyond what the derive macro can. +// See +impl PartialOrd for Entity { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + // Make use of our `Ord` impl to ensure optimal codegen output + Some(self.cmp(other)) + } +} + +// The derive macro codegen output is not optimal and can't be optimised as well +// by the compiler. This impl resolves the issue of non-optimal codegen by relying +// on comparing against the bit representation of `Entity` instead of comparing +// the fields. The result is then LLVM is able to optimise the codegen for Entity +// far beyond what the derive macro can. +// See +impl Ord for Entity { + #[inline] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + // This will result in better codegen for ordering comparisons, plus + // avoids pitfalls with regards to macro codegen relying on property + // position when we want to compare against the bit representation. + self.to_bits().cmp(&other.to_bits()) + } +} + impl Hash for Entity { #[inline] fn hash(&self, state: &mut H) { @@ -188,6 +240,7 @@ impl Entity { /// 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. + #[inline] pub const fn from_raw(index: u32) -> Entity { Entity { index, @@ -201,6 +254,7 @@ impl Entity { /// for serialization between runs. /// /// No particular structure is guaranteed for the returned bits. + #[inline(always)] pub const fn to_bits(self) -> u64 { (self.generation as u64) << 32 | self.index as u64 } @@ -208,6 +262,7 @@ impl Entity { /// Reconstruct an `Entity` previously destructured with [`Entity::to_bits`]. /// /// Only useful when applied to results from `to_bits` in the same instance of an application. + #[inline(always)] pub const fn from_bits(bits: u64) -> Self { Self { generation: (bits >> 32) as u32, @@ -917,4 +972,30 @@ mod tests { assert_eq!(next_entity.index(), entity.index()); assert!(next_entity.generation > entity.generation + GENERATIONS); } + + #[test] + fn entity_comparison() { + // This is intentionally testing `lt` and `ge` as separate functions. + #![allow(clippy::nonminimal_bool)] + + assert!(Entity::new(123, 456) == Entity::new(123, 456)); + assert!(Entity::new(123, 789) != Entity::new(123, 456)); + assert!(Entity::new(123, 456) != Entity::new(123, 789)); + assert!(Entity::new(123, 456) != Entity::new(456, 123)); + + // ordering is by generation then by index + + assert!(Entity::new(123, 456) >= Entity::new(123, 456)); + assert!(Entity::new(123, 456) <= Entity::new(123, 456)); + assert!(!(Entity::new(123, 456) < Entity::new(123, 456))); + assert!(!(Entity::new(123, 456) > Entity::new(123, 456))); + + assert!(Entity::new(9, 1) < Entity::new(1, 9)); + assert!(Entity::new(1, 9) > Entity::new(9, 1)); + + assert!(Entity::new(1, 1) < Entity::new(2, 1)); + assert!(Entity::new(1, 1) <= Entity::new(2, 1)); + assert!(Entity::new(2, 2) > Entity::new(1, 2)); + assert!(Entity::new(2, 2) >= Entity::new(1, 2)); + } } diff --git a/crates/bevy_ecs/src/event.rs b/crates/bevy_ecs/src/event.rs index 781bbde448cd0..222c6101ceee4 100644 --- a/crates/bevy_ecs/src/event.rs +++ b/crates/bevy_ecs/src/event.rs @@ -191,7 +191,8 @@ impl Events { /// "Sends" an `event` by writing it to the current event buffer. [`EventReader`]s can then read /// the event. - pub fn send(&mut self, event: E) { + /// This method returns the [ID](`EventId`) of the sent `event`. + pub fn send(&mut self, event: E) -> EventId { let event_id = EventId { id: self.event_count, _marker: PhantomData, @@ -202,14 +203,32 @@ impl Events { self.events_b.push(event_instance); self.event_count += 1; + + event_id + } + + /// Sends a list of `events` all at once, which can later be read by [`EventReader`]s. + /// This is more efficient than sending each event individually. + /// This method returns the [IDs](`EventId`) of the sent `events`. + pub fn send_batch(&mut self, events: impl IntoIterator) -> SendBatchIds { + let last_count = self.event_count; + + self.extend(events); + + SendBatchIds { + last_count, + event_count: self.event_count, + _marker: PhantomData, + } } /// Sends the default value of the event. Useful when the event is an empty struct. - pub fn send_default(&mut self) + /// This method returns the [ID](`EventId`) of the sent `event`. + pub fn send_default(&mut self) -> EventId where E: Default, { - self.send(Default::default()); + self.send(Default::default()) } /// Gets a new [`ManualEventReader`]. This will include all events already in the event buffers. @@ -512,26 +531,31 @@ pub struct EventWriter<'w, E: Event> { impl<'w, E: Event> EventWriter<'w, E> { /// Sends an `event`, which can later be read by [`EventReader`]s. + /// This method returns the [ID](`EventId`) of the sent `event`. /// /// See [`Events`] for details. - pub fn send(&mut self, event: E) { - self.events.send(event); + pub fn send(&mut self, event: E) -> EventId { + self.events.send(event) } /// Sends a list of `events` all at once, which can later be read by [`EventReader`]s. /// This is more efficient than sending each event individually. + /// This method returns the [IDs](`EventId`) of the sent `events`. /// /// See [`Events`] for details. - pub fn send_batch(&mut self, events: impl IntoIterator) { - self.events.extend(events); + pub fn send_batch(&mut self, events: impl IntoIterator) -> SendBatchIds { + self.events.send_batch(events) } /// Sends the default value of the event. Useful when the event is an empty struct. - pub fn send_default(&mut self) + /// This method returns the [ID](`EventId`) of the sent `event`. + /// + /// See [`Events`] for details. + pub fn send_default(&mut self) -> EventId where E: Default, { - self.events.send_default(); + self.events.send_default() } } @@ -760,6 +784,38 @@ pub fn event_update_condition(events: Res>) -> bool { !events.events_a.is_empty() || !events.events_b.is_empty() } +/// [`Iterator`] over sent [`EventIds`](`EventId`) from a batch. +pub struct SendBatchIds { + last_count: usize, + event_count: usize, + _marker: PhantomData, +} + +impl Iterator for SendBatchIds { + type Item = EventId; + + fn next(&mut self) -> Option { + if self.last_count >= self.event_count { + return None; + } + + let result = Some(EventId { + id: self.last_count, + _marker: PhantomData, + }); + + self.last_count += 1; + + result + } +} + +impl ExactSizeIterator for SendBatchIds { + fn len(&self) -> usize { + self.event_count.saturating_sub(self.last_count) + } +} + #[cfg(test)] mod tests { use crate::system::assert_is_read_only_system; @@ -1119,4 +1175,43 @@ mod tests { assert_is_read_only_system(reader_system); } + + #[test] + fn test_send_events_ids() { + let mut events = Events::::default(); + let event_0 = TestEvent { i: 0 }; + let event_1 = TestEvent { i: 1 }; + let event_2 = TestEvent { i: 2 }; + + let event_0_id = events.send(event_0); + + assert_eq!( + events.get_event(event_0_id.id), + Some((&event_0, event_0_id)), + "Getting a sent event by ID should return the original event" + ); + + let mut event_ids = events.send_batch([event_1, event_2]); + + let event_id = event_ids.next().expect("Event 1 must have been sent"); + + assert_eq!( + events.get_event(event_id.id), + Some((&event_1, event_id)), + "Getting a sent event by ID should return the original event" + ); + + let event_id = event_ids.next().expect("Event 2 must have been sent"); + + assert_eq!( + events.get_event(event_id.id), + Some((&event_2, event_id)), + "Getting a sent event by ID should return the original event" + ); + + assert!( + event_ids.next().is_none(), + "Only sent two events; got more than two IDs" + ); + } } diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index f971697beb539..f4e644506f982 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1,6 +1,4 @@ -#![warn(clippy::undocumented_unsafe_blocks)] #![warn(missing_docs)] -#![allow(clippy::type_complexity)] #![doc = include_str!("../README.md")] #[cfg(target_pointer_width = "16")] @@ -1725,4 +1723,22 @@ mod tests { "new entity was spawned and received C component" ); } + + #[derive(Component)] + struct ComponentA(u32); + + #[derive(Component)] + struct ComponentB(u32); + + #[derive(Bundle)] + struct Simple(ComponentA); + + #[derive(Bundle)] + struct Tuple(Simple, ComponentB); + + #[derive(Bundle)] + struct Record { + field0: Simple, + field1: ComponentB, + } } diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index 1c60f5b3da700..3ce86039ad0f8 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -206,6 +206,39 @@ impl NodeConfigs { } /// Types that can convert into a [`SystemConfigs`]. +/// +/// This trait is implemented for "systems" (functions whose arguments all implement +/// [`SystemParam`](crate::system::SystemParam)), or tuples thereof. +/// It is a common entry point for system configurations. +/// +/// # Examples +/// +/// ``` +/// # use bevy_ecs::schedule::IntoSystemConfigs; +/// # struct AppMock; +/// # struct Update; +/// # impl AppMock { +/// # pub fn add_systems( +/// # &mut self, +/// # schedule: Update, +/// # systems: impl IntoSystemConfigs, +/// # ) -> &mut Self { self } +/// # } +/// # let mut app = AppMock; +/// +/// fn handle_input() {} +/// +/// fn update_camera() {} +/// fn update_character() {} +/// +/// app.add_systems( +/// Update, +/// ( +/// handle_input, +/// (update_camera, update_character).after(handle_input) +/// ) +/// ); +/// ``` pub trait IntoSystemConfigs where Self: Sized, @@ -221,11 +254,17 @@ where } /// Run before all systems in `set`. + /// + /// Note: The given set is not implicitly added to the schedule when this system set is added. + /// It is safe, but no dependencies will be created. fn before(self, set: impl IntoSystemSet) -> SystemConfigs { self.into_configs().before(set) } /// Run after all systems in `set`. + /// + /// Note: The given set is not implicitly added to the schedule when this system set is added. + /// It is safe, but no dependencies will be created. fn after(self, set: impl IntoSystemSet) -> SystemConfigs { self.into_configs().after(set) } diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 82732b1a7e052..8d65f61119e61 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -859,10 +859,6 @@ impl ScheduleGraph { self.dependency.graph.add_node(set); } - if !self.dependency.graph.contains_node(id) { - self.dependency.graph.add_node(id); - } - for (kind, set) in dependencies .into_iter() .map(|Dependency { kind, set }| (kind, self.system_set_ids[&set])) diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/system/commands/command_queue.rs index b32ff7a8134e5..dab6a916662ec 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/system/commands/command_queue.rs @@ -97,18 +97,18 @@ impl CommandQueue { // flush the previously queued entities world.flush(); - // Pointer that will iterate over the entries of the buffer. - let mut cursor = self.bytes.as_mut_ptr(); + // The range of pointers of the filled portion of `self.bytes`. + let bytes_range = self.bytes.as_mut_ptr_range(); - // The address of the end of the buffer. - let end_addr = cursor as usize + self.bytes.len(); + // Pointer that will iterate over the entries of the buffer. + let mut cursor = bytes_range.start; // Reset the buffer, so it can be reused after this function ends. // In the loop below, ownership of each command will be transferred into user code. // SAFETY: `set_len(0)` is always valid. unsafe { self.bytes.set_len(0) }; - while (cursor as usize) < end_addr { + while cursor < bytes_range.end { // SAFETY: The cursor is either at the start of the buffer, or just after the previous command. // Since we know that the cursor is in bounds, it must point to the start of a new command. let meta = unsafe { cursor.cast::().read_unaligned() }; @@ -136,6 +136,11 @@ impl CommandQueue { cursor = unsafe { cursor.add(size) }; } } + + /// Take all commands from `other` and append them to `self`, leaving `other` empty + pub fn append(&mut self, other: &mut CommandQueue) { + self.bytes.append(&mut other.bytes); + } } #[cfg(test)] diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 89c8493dc667b..1f9421474f0e8 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -5,7 +5,7 @@ use crate::{ self as bevy_ecs, bundle::Bundle, entity::{Entities, Entity}, - system::{RunSystem, SystemId}, + system::{RunSystemWithInput, SystemId}, world::{EntityWorldMut, FromWorld, World}, }; use bevy_ecs_macros::SystemParam; @@ -146,6 +146,11 @@ impl<'w, 's> Commands<'w, 's> { } } + /// Take all commands from `other` and append them to `self`, leaving `other` empty + pub fn append(&mut self, other: &mut CommandQueue) { + self.queue.append(other); + } + /// Pushes a [`Command`] to the queue for creating a new empty [`Entity`], /// and returns its corresponding [`EntityCommands`]. /// @@ -523,8 +528,26 @@ impl<'w, 's> Commands<'w, 's> { /// Running slow systems can become a bottleneck. /// /// Calls [`World::run_system`](crate::system::World::run_system). + /// + /// There is no way to get the output of a system when run as a command, because the + /// execution of the system happens later. To get the output of a system, use + /// [`World::run_system`] or [`World::run_system_with_input`] instead of running the system as a command. pub fn run_system(&mut self, id: SystemId) { - self.queue.push(RunSystem::new(id)); + self.run_system_with_input(id, ()); + } + + /// Runs the system corresponding to the given [`SystemId`]. + /// Systems are ran in an exclusive and single threaded way. + /// Running slow systems can become a bottleneck. + /// + /// Calls [`World::run_system_with_input`](crate::system::World::run_system_with_input). + /// + /// There is no way to get the output of a system when run as a command, because the + /// execution of the system happens later. To get the output of a system, use + /// [`World::run_system`] or [`World::run_system_with_input`] instead of running the system as a command. + pub fn run_system_with_input(&mut self, id: SystemId, input: I) { + self.queue + .push(RunSystemWithInput::new_with_input(id, input)); } /// Pushes a generic [`Command`] to the command queue. @@ -1303,4 +1326,23 @@ mod tests { assert!(!world.contains_resource::>()); assert!(world.contains_resource::>()); } + + #[test] + fn append() { + let mut world = World::default(); + let mut queue_1 = CommandQueue::default(); + { + let mut commands = Commands::new(&mut queue_1, &world); + commands.insert_resource(W(123i32)); + } + let mut queue_2 = CommandQueue::default(); + { + let mut commands = Commands::new(&mut queue_2, &world); + commands.insert_resource(W(456.0f64)); + } + queue_1.append(&mut queue_2); + queue_1.apply(&mut world); + assert!(world.contains_resource::>()); + assert!(world.contains_resource::>()); + } } diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 8750e44d26334..a8578ae3d4357 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -155,8 +155,8 @@ pub trait IntoSystem: Sized { /// Pass the output of this system `A` into a second system `B`, creating a new compound system. /// - /// The second system must have `In` as its first parameter, where `T` - /// is the return type of the first system. + /// The second system must have [`In`](crate::system::In) as its first parameter, + /// where `T` is the return type of the first system. fn pipe(self, system: B) -> PipeSystem where B: IntoSystem, diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index 05a01719fcd2c..54e8e6e177dc8 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -7,21 +7,21 @@ use thiserror::Error; /// A small wrapper for [`BoxedSystem`] that also keeps track whether or not the system has been initialized. #[derive(Component)] -struct RegisteredSystem { +struct RegisteredSystem { initialized: bool, - system: BoxedSystem, + system: BoxedSystem, } /// A system that has been removed from the registry. /// It contains the system and whether or not it has been initialized. /// /// This struct is returned by [`World::remove_system`]. -pub struct RemovedSystem { +pub struct RemovedSystem { initialized: bool, - system: BoxedSystem, + system: BoxedSystem, } -impl RemovedSystem { +impl RemovedSystem { /// Is the system initialized? /// A system is initialized the first time it's ran. pub fn initialized(&self) -> bool { @@ -29,7 +29,7 @@ impl RemovedSystem { } /// The system removed from the storage. - pub fn system(self) -> BoxedSystem { + pub fn system(self) -> BoxedSystem { self.system } } @@ -38,8 +38,41 @@ impl RemovedSystem { /// /// These are opaque identifiers, keyed to a specific [`World`], /// and are created via [`World::register_system`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct SystemId(Entity); +#[derive(Eq)] +pub struct SystemId(Entity, std::marker::PhantomData O>); + +// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters. +impl Copy for SystemId {} + +// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters. +impl Clone for SystemId { + fn clone(&self) -> Self { + *self + } +} + +// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters. +impl PartialEq for SystemId { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 && self.1 == other.1 + } +} + +// A manual impl is used because the trait bounds should ignore the `I` and `O` phantom parameters. +impl std::hash::Hash for SystemId { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + +impl std::fmt::Debug for SystemId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("SystemId") + .field(&self.0) + .field(&self.1) + .finish() + } +} impl World { /// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`]. @@ -51,10 +84,10 @@ impl World { /// This allows for running systems in a pushed-based fashion. /// Using a [`Schedule`](crate::schedule::Schedule) is still preferred for most cases /// due to its better performance and abillity to run non-conflicting systems simultaneously. - pub fn register_system + 'static>( + pub fn register_system + 'static>( &mut self, system: S, - ) -> SystemId { + ) -> SystemId { self.register_boxed_system(Box::new(IntoSystem::into_system(system))) } @@ -62,13 +95,17 @@ impl World { /// /// This is useful if the [`IntoSystem`] implementor has already been turned into a /// [`System`](crate::system::System) trait object and put in a [`Box`]. - pub fn register_boxed_system(&mut self, system: BoxedSystem) -> SystemId { + pub fn register_boxed_system( + &mut self, + system: BoxedSystem, + ) -> SystemId { SystemId( self.spawn(RegisteredSystem { initialized: false, system, }) .id(), + std::marker::PhantomData, ) } @@ -78,11 +115,14 @@ impl World { /// /// If no system corresponds to the given [`SystemId`], this method returns an error. /// Systems are also not allowed to remove themselves, this returns an error too. - pub fn remove_system(&mut self, id: SystemId) -> Result { + pub fn remove_system( + &mut self, + id: SystemId, + ) -> Result, RegisteredSystemError> { match self.get_entity_mut(id.0) { Some(mut entity) => { let registered_system = entity - .take::() + .take::>() .ok_or(RegisteredSystemError::SelfRemove(id))?; entity.despawn(); Ok(RemovedSystem { @@ -100,14 +140,17 @@ impl World { /// This is different from [`RunSystemOnce::run_system_once`](crate::system::RunSystemOnce::run_system_once), /// because it keeps local state between calls and change detection works correctly. /// + /// In order to run a chained system with an input, use [`World::run_system_with_input`] instead. + /// /// # Limitations /// - /// - Stored systems cannot be chained: they can neither have an [`In`](crate::system::In) nor return any values. /// - Stored systems cannot be recursive, they cannot call themselves through [`Commands::run_system`](crate::system::Commands). /// - Exclusive systems cannot be used. /// /// # Examples /// + /// ## Running a system + /// /// ```rust /// # use bevy_ecs::prelude::*; /// #[derive(Resource, Default)] @@ -126,7 +169,7 @@ impl World { /// world.run_system(counter_two); // -> 1 /// ``` /// - /// Change detection: + /// ## Change detection /// /// ```rust /// # use bevy_ecs::prelude::*; @@ -149,7 +192,81 @@ impl World { /// world.resource_mut::().set_changed(); /// let _ = world.run_system(detector); // -> Something happened! /// ``` - pub fn run_system(&mut self, id: SystemId) -> Result<(), RegisteredSystemError> { + /// + /// ## Getting system output + /// + /// ```rust + /// # use bevy_ecs::prelude::*; + /// + /// #[derive(Resource)] + /// struct PlayerScore(i32); + /// + /// #[derive(Resource)] + /// struct OpponentScore(i32); + /// + /// fn get_player_score(player_score: Res) -> i32 { + /// player_score.0 + /// } + /// + /// fn get_opponent_score(opponent_score: Res) -> i32 { + /// opponent_score.0 + /// } + /// + /// let mut world = World::default(); + /// world.insert_resource(PlayerScore(3)); + /// world.insert_resource(OpponentScore(2)); + /// + /// let scoring_systems = [ + /// ("player", world.register_system(get_player_score)), + /// ("opponent", world.register_system(get_opponent_score)), + /// ]; + /// + /// for (label, scoring_system) in scoring_systems { + /// println!("{label} has score {}", world.run_system(scoring_system).expect("system succeeded")); + /// } + /// ``` + pub fn run_system( + &mut self, + id: SystemId<(), O>, + ) -> Result> { + self.run_system_with_input(id, ()) + } + + /// Run a stored chained system by its [`SystemId`], providing an input value. + /// Before running a system, it must first be registered. + /// The method [`World::register_system`] stores a given system and returns a [`SystemId`]. + /// + /// # Limitations + /// + /// - Stored systems cannot be recursive, they cannot call themselves through [`Commands::run_system`](crate::system::Commands). + /// - Exclusive systems cannot be used. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_ecs::prelude::*; + /// #[derive(Resource, Default)] + /// struct Counter(u8); + /// + /// fn increment(In(increment_by): In, mut counter: Local) { + /// counter.0 += increment_by; + /// println!("{}", counter.0); + /// } + /// + /// let mut world = World::default(); + /// let counter_one = world.register_system(increment); + /// let counter_two = world.register_system(increment); + /// world.run_system_with_input(counter_one, 1); // -> 1 + /// world.run_system_with_input(counter_one, 20); // -> 21 + /// world.run_system_with_input(counter_two, 30); // -> 51 + /// ``` + /// + /// See [`World::run_system`] for more examples. + pub fn run_system_with_input( + &mut self, + id: SystemId, + input: I, + ) -> Result> { // lookup let mut entity = self .get_entity_mut(id.0) @@ -160,7 +277,7 @@ impl World { mut initialized, mut system, } = entity - .take::() + .take::>() .ok_or(RegisteredSystemError::Recursive(id))?; // run the system @@ -168,57 +285,98 @@ impl World { system.initialize(self); initialized = true; } - system.run((), self); + let result = system.run(input, self); system.apply_deferred(self); // return ownership of system trait object (if entity still exists) if let Some(mut entity) = self.get_entity_mut(id.0) { - entity.insert::(RegisteredSystem { + entity.insert::>(RegisteredSystem { initialized, system, }); } - Ok(()) + Ok(result) } } -/// The [`Command`] type for [`World::run_system`]. +/// The [`Command`] type for [`World::run_system`] or [`World::run_system_with_input`]. /// /// This command runs systems in an exclusive and single threaded way. /// Running slow systems can become a bottleneck. +/// +/// If the system needs an [`In<_>`](crate::system::In) input value to run, it must +/// be provided as part of the command. +/// +/// There is no way to get the output of a system when run as a command, because the +/// execution of the system happens later. To get the output of a system, use +/// [`World::run_system`] or [`World::run_system_with_input`] instead of running the system as a command. #[derive(Debug, Clone)] -pub struct RunSystem { - system_id: SystemId, +pub struct RunSystemWithInput { + system_id: SystemId, + input: I, } +/// The [`Command`] type for [`World::run_system`]. +/// +/// This command runs systems in an exclusive and single threaded way. +/// Running slow systems can become a bottleneck. +/// +/// If the system needs an [`In<_>`](crate::system::In) input value to run, use the +/// [`crate::system::RunSystemWithInput`] type instead. +/// +/// There is no way to get the output of a system when run as a command, because the +/// execution of the system happens later. To get the output of a system, use +/// [`World::run_system`] or [`World::run_system_with_input`] instead of running the system as a command. +pub type RunSystem = RunSystemWithInput<()>; + impl RunSystem { /// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands) pub fn new(system_id: SystemId) -> Self { - Self { system_id } + Self::new_with_input(system_id, ()) + } +} + +impl RunSystemWithInput { + /// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands) + /// in order to run the specified system with the provided [`In<_>`](crate::system::In) input value. + pub fn new_with_input(system_id: SystemId, input: I) -> Self { + Self { system_id, input } } } -impl Command for RunSystem { +impl Command for RunSystemWithInput { #[inline] fn apply(self, world: &mut World) { - let _ = world.run_system(self.system_id); + let _ = world.run_system_with_input(self.system_id, self.input); } } /// An operation with stored systems failed. -#[derive(Debug, Error)] -pub enum RegisteredSystemError { +#[derive(Error)] +pub enum RegisteredSystemError { /// A system was run by id, but no system with that id was found. /// /// Did you forget to register it? #[error("System {0:?} was not registered")] - SystemIdNotRegistered(SystemId), + SystemIdNotRegistered(SystemId), /// A system tried to run itself recursively. #[error("System {0:?} tried to run itself recursively")] - Recursive(SystemId), + Recursive(SystemId), /// A system tried to remove itself. #[error("System {0:?} tried to remove itself")] - SelfRemove(SystemId), + SelfRemove(SystemId), +} + +impl std::fmt::Debug for RegisteredSystemError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::SystemIdNotRegistered(arg0) => { + f.debug_tuple("SystemIdNotRegistered").field(arg0).finish() + } + Self::Recursive(arg0) => f.debug_tuple("Recursive").field(arg0).finish(), + Self::SelfRemove(arg0) => f.debug_tuple("SelfRemove").field(arg0).finish(), + } + } } mod tests { @@ -248,14 +406,14 @@ mod tests { assert_eq!(*world.resource::(), Counter(0)); // Resources are changed when they are first added. let id = world.register_system(count_up_iff_changed); - let _ = world.run_system(id); + world.run_system(id).expect("system runs successfully"); assert_eq!(*world.resource::(), Counter(1)); // Nothing changed - let _ = world.run_system(id); + world.run_system(id).expect("system runs successfully"); assert_eq!(*world.resource::(), Counter(1)); // Making a change world.resource_mut::().set_changed(); - let _ = world.run_system(id); + world.run_system(id).expect("system runs successfully"); assert_eq!(*world.resource::(), Counter(2)); } @@ -271,16 +429,82 @@ mod tests { world.insert_resource(Counter(1)); assert_eq!(*world.resource::(), Counter(1)); let id = world.register_system(doubling); - let _ = world.run_system(id); + world.run_system(id).expect("system runs successfully"); assert_eq!(*world.resource::(), Counter(1)); - let _ = world.run_system(id); + world.run_system(id).expect("system runs successfully"); assert_eq!(*world.resource::(), Counter(2)); - let _ = world.run_system(id); + world.run_system(id).expect("system runs successfully"); assert_eq!(*world.resource::(), Counter(4)); - let _ = world.run_system(id); + world.run_system(id).expect("system runs successfully"); assert_eq!(*world.resource::(), Counter(8)); } + #[test] + fn input_values() { + // Verify that a non-Copy, non-Clone type can be passed in. + struct NonCopy(u8); + + fn increment_sys(In(NonCopy(increment_by)): In, mut counter: ResMut) { + counter.0 += increment_by; + } + + let mut world = World::new(); + + let id = world.register_system(increment_sys); + + // Insert the resource after registering the system. + world.insert_resource(Counter(1)); + assert_eq!(*world.resource::(), Counter(1)); + + world + .run_system_with_input(id, NonCopy(1)) + .expect("system runs successfully"); + assert_eq!(*world.resource::(), Counter(2)); + + world + .run_system_with_input(id, NonCopy(1)) + .expect("system runs successfully"); + assert_eq!(*world.resource::(), Counter(3)); + + world + .run_system_with_input(id, NonCopy(20)) + .expect("system runs successfully"); + assert_eq!(*world.resource::(), Counter(23)); + + world + .run_system_with_input(id, NonCopy(1)) + .expect("system runs successfully"); + assert_eq!(*world.resource::(), Counter(24)); + } + + #[test] + fn output_values() { + // Verify that a non-Copy, non-Clone type can be returned. + #[derive(Eq, PartialEq, Debug)] + struct NonCopy(u8); + + fn increment_sys(mut counter: ResMut) -> NonCopy { + counter.0 += 1; + NonCopy(counter.0) + } + + let mut world = World::new(); + + let id = world.register_system(increment_sys); + + // Insert the resource after registering the system. + world.insert_resource(Counter(1)); + assert_eq!(*world.resource::(), Counter(1)); + + let output = world.run_system(id).expect("system runs successfully"); + assert_eq!(*world.resource::(), Counter(2)); + assert_eq!(output, NonCopy(2)); + + let output = world.run_system(id).expect("system runs successfully"); + assert_eq!(*world.resource::(), Counter(3)); + assert_eq!(output, NonCopy(3)); + } + #[test] fn nested_systems() { use crate::system::SystemId; @@ -310,4 +534,32 @@ mod tests { let _ = world.run_system(nested_id); assert_eq!(*world.resource::(), Counter(5)); } + + #[test] + fn nested_systems_with_inputs() { + use crate::system::SystemId; + + #[derive(Component)] + struct Callback(SystemId, u8); + + fn nested(query: Query<&Callback>, mut commands: Commands) { + for callback in query.iter() { + commands.run_system_with_input(callback.0, callback.1); + } + } + + let mut world = World::new(); + world.insert_resource(Counter(0)); + + let increment_by = + world.register_system(|In(amt): In, mut counter: ResMut| { + counter.0 += amt; + }); + let nested_id = world.register_system(nested); + + world.spawn(Callback(increment_by, 2)); + world.spawn(Callback(increment_by, 3)); + let _ = world.run_system(nested_id); + assert_eq!(*world.resource::(), Counter(5)); + } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 274daeed05547..c94b45dad10ee 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -10,7 +10,7 @@ use crate::{ }; use bevy_ptr::{OwningPtr, Ptr}; use bevy_utils::tracing::debug; -use std::any::TypeId; +use std::{any::TypeId, marker::PhantomData}; use super::{unsafe_world_cell::UnsafeEntityCell, Ref}; @@ -1032,6 +1032,391 @@ impl<'w> EntityWorldMut<'w> { pub fn update_location(&mut self) { self.location = self.world.entities().get(self.entity).unwrap(); } + + /// Gets an Entry into the world for this entity and component for in-place manipulation. + /// + /// The type parameter specifies which component to get. + /// + /// # Examples + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] + /// struct Comp(u32); + /// + /// # let mut world = World::new(); + /// let mut entity = world.spawn_empty(); + /// entity.entry().or_insert_with(|| Comp(4)); + /// # let entity_id = entity.id(); + /// assert_eq!(world.query::<&Comp>().single(&world).0, 4); + /// + /// # let mut entity = world.get_entity_mut(entity_id).unwrap(); + /// entity.entry::().and_modify(|mut c| c.0 += 1); + /// assert_eq!(world.query::<&Comp>().single(&world).0, 5); + /// + /// ``` + pub fn entry<'a, T: Component>(&'a mut self) -> Entry<'w, 'a, T> { + if self.contains::() { + Entry::Occupied(OccupiedEntry { + entity_world: self, + _marker: PhantomData, + }) + } else { + Entry::Vacant(VacantEntry { + entity_world: self, + _marker: PhantomData, + }) + } + } +} + +/// A view into a single entity and component in a world, which may either be vacant or occupied. +/// +/// This `enum` can only be constructed from the [`entry`] method on [`EntityWorldMut`]. +/// +/// [`entry`]: EntityWorldMut::entry +pub enum Entry<'w, 'a, T: Component> { + /// An occupied entry. + Occupied(OccupiedEntry<'w, 'a, T>), + /// A vacant entry. + Vacant(VacantEntry<'w, 'a, T>), +} + +impl<'w, 'a, T: Component> Entry<'w, 'a, T> { + /// Provides in-place mutable access to an occupied entry. + /// + /// # Examples + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] + /// struct Comp(u32); + /// + /// # let mut world = World::new(); + /// let mut entity = world.spawn(Comp(0)); + /// + /// entity.entry::().and_modify(|mut c| c.0 += 1); + /// assert_eq!(world.query::<&Comp>().single(&world).0, 1); + /// ``` + #[inline] + pub fn and_modify)>(self, f: F) -> Self { + match self { + Entry::Occupied(mut entry) => { + f(entry.get_mut()); + Entry::Occupied(entry) + } + Entry::Vacant(entry) => Entry::Vacant(entry), + } + } + + /// Replaces the component of the entry, and returns an [`OccupiedEntry`]. + /// + /// # Examples + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] + /// struct Comp(u32); + /// + /// # let mut world = World::new(); + /// let mut entity = world.spawn_empty(); + /// + /// let entry = entity.entry().insert_entry(Comp(4)); + /// assert_eq!(entry.get(), &Comp(4)); + /// + /// let entry = entity.entry().insert_entry(Comp(2)); + /// assert_eq!(entry.get(), &Comp(2)); + /// ``` + #[inline] + pub fn insert_entry(self, component: T) -> OccupiedEntry<'w, 'a, T> { + match self { + Entry::Occupied(mut entry) => { + entry.insert(component); + entry + } + Entry::Vacant(entry) => entry.insert_entry(component), + } + } + + /// Ensures the entry has this component by inserting the given default if empty, and + /// returns a mutable reference to this component in the entry. + /// + /// # Examples + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] + /// struct Comp(u32); + /// + /// # let mut world = World::new(); + /// let mut entity = world.spawn_empty(); + /// + /// entity.entry().or_insert(Comp(4)); + /// # let entity_id = entity.id(); + /// assert_eq!(world.query::<&Comp>().single(&world).0, 4); + /// + /// # let mut entity = world.get_entity_mut(entity_id).unwrap(); + /// entity.entry().or_insert(Comp(15)).0 *= 2; + /// assert_eq!(world.query::<&Comp>().single(&world).0, 8); + /// ``` + #[inline] + pub fn or_insert(self, default: T) -> Mut<'a, T> { + match self { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => entry.insert(default), + } + } + + /// Ensures the entry has this component by inserting the result of the default function if + /// empty, and returns a mutable reference to this component in the entry. + /// + /// # Examples + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] + /// struct Comp(u32); + /// + /// # let mut world = World::new(); + /// let mut entity = world.spawn_empty(); + /// + /// entity.entry().or_insert_with(|| Comp(4)); + /// assert_eq!(world.query::<&Comp>().single(&world).0, 4); + /// ``` + #[inline] + pub fn or_insert_with T>(self, default: F) -> Mut<'a, T> { + match self { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => entry.insert(default()), + } + } +} + +impl<'w, 'a, T: Component + Default> Entry<'w, 'a, T> { + /// Ensures the entry has this component by inserting the default value if empty, and + /// returns a mutable reference to this component in the entry. + /// + /// # Examples + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] + /// struct Comp(u32); + /// + /// # let mut world = World::new(); + /// let mut entity = world.spawn_empty(); + /// + /// entity.entry::().or_default(); + /// assert_eq!(world.query::<&Comp>().single(&world).0, 0); + /// ``` + #[inline] + pub fn or_default(self) -> Mut<'a, T> { + match self { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => entry.insert(Default::default()), + } + } +} + +/// A view into an occupied entry in a [`EntityWorldMut`]. It is part of the [`Entry`] enum. +/// +/// The contained entity must have the component type parameter if we have this struct. +pub struct OccupiedEntry<'w, 'a, T: Component> { + entity_world: &'a mut EntityWorldMut<'w>, + _marker: PhantomData, +} + +impl<'w, 'a, T: Component> OccupiedEntry<'w, 'a, T> { + /// Gets a reference to the component in the entry. + /// + /// # Examples + /// + /// ``` + /// # use bevy_ecs::{prelude::*, world::Entry}; + /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] + /// struct Comp(u32); + /// + /// # let mut world = World::new(); + /// let mut entity = world.spawn(Comp(5)); + /// + /// if let Entry::Occupied(o) = entity.entry::() { + /// assert_eq!(o.get().0, 5); + /// } + /// ``` + #[inline] + pub fn get(&self) -> &T { + // This shouldn't panic because if we have an OccupiedEntry the component must exist. + self.entity_world.get::().unwrap() + } + + /// Gets a mutable reference to the component in the entry. + /// + /// If you need a reference to the `OccupiedEntry` which may outlive the destruction of + /// the `Entry` value, see [`into_mut`]. + /// + /// [`into_mut`]: Self::into_mut + /// + /// # Examples + /// + /// ``` + /// # use bevy_ecs::{prelude::*, world::Entry}; + /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] + /// struct Comp(u32); + /// + /// # let mut world = World::new(); + /// let mut entity = world.spawn(Comp(5)); + /// + /// if let Entry::Occupied(mut o) = entity.entry::() { + /// o.get_mut().0 += 10; + /// assert_eq!(o.get().0, 15); + /// + /// // We can use the same Entry multiple times. + /// o.get_mut().0 += 2 + /// } + /// + /// assert_eq!(world.query::<&Comp>().single(&world).0, 17); + /// ``` + #[inline] + pub fn get_mut(&mut self) -> Mut<'_, T> { + // This shouldn't panic because if we have an OccupiedEntry the component must exist. + self.entity_world.get_mut::().unwrap() + } + + /// Converts the `OccupiedEntry` into a mutable reference to the value in the entry with + /// a lifetime bound to the `EntityWorldMut`. + /// + /// If you need multiple references to the `OccupiedEntry`, see [`get_mut`]. + /// + /// [`get_mut`]: Self::get_mut + /// + /// # Examples + /// + /// ``` + /// # use bevy_ecs::{prelude::*, world::Entry}; + /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] + /// struct Comp(u32); + /// + /// # let mut world = World::new(); + /// let mut entity = world.spawn(Comp(5)); + /// + /// if let Entry::Occupied(o) = entity.entry::() { + /// o.into_mut().0 += 10; + /// } + /// + /// assert_eq!(world.query::<&Comp>().single(&world).0, 15); + /// ``` + #[inline] + pub fn into_mut(self) -> Mut<'a, T> { + // This shouldn't panic because if we have an OccupiedEntry the component must exist. + self.entity_world.get_mut().unwrap() + } + + /// Replaces the component of the entry. + /// + /// # Examples + /// + /// ``` + /// # use bevy_ecs::{prelude::*, world::Entry}; + /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] + /// struct Comp(u32); + /// + /// # let mut world = World::new(); + /// let mut entity = world.spawn(Comp(5)); + /// + /// if let Entry::Occupied(mut o) = entity.entry::() { + /// o.insert(Comp(10)); + /// } + /// + /// assert_eq!(world.query::<&Comp>().single(&world).0, 10); + /// ``` + #[inline] + pub fn insert(&mut self, component: T) { + self.entity_world.insert(component); + } + + /// Removes the component from the entry and returns it. + /// + /// # Examples + /// + /// ``` + /// # use bevy_ecs::{prelude::*, world::Entry}; + /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] + /// struct Comp(u32); + /// + /// # let mut world = World::new(); + /// let mut entity = world.spawn(Comp(5)); + /// + /// if let Entry::Occupied(o) = entity.entry::() { + /// assert_eq!(o.take(), Comp(5)); + /// } + /// + /// assert_eq!(world.query::<&Comp>().iter(&world).len(), 0); + /// ``` + #[inline] + pub fn take(self) -> T { + // This shouldn't panic because if we have an OccupiedEntry the component must exist. + self.entity_world.take().unwrap() + } +} + +/// A view into a vacant entry in a [`EntityWorldMut`]. It is part of the [`Entry`] enum. +pub struct VacantEntry<'w, 'a, T: Component> { + entity_world: &'a mut EntityWorldMut<'w>, + _marker: PhantomData, +} + +impl<'w, 'a, T: Component> VacantEntry<'w, 'a, T> { + /// Inserts the component into the `VacantEntry` and returns a mutable reference to it. + /// + /// # Examples + /// + /// ``` + /// # use bevy_ecs::{prelude::*, world::Entry}; + /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] + /// struct Comp(u32); + /// + /// # let mut world = World::new(); + /// let mut entity = world.spawn_empty(); + /// + /// if let Entry::Vacant(v) = entity.entry::() { + /// v.insert(Comp(10)); + /// } + /// + /// assert_eq!(world.query::<&Comp>().single(&world).0, 10); + /// ``` + #[inline] + pub fn insert(self, component: T) -> Mut<'a, T> { + self.entity_world.insert(component); + // This shouldn't panic because we just added this component + self.entity_world.get_mut::().unwrap() + } + + /// Inserts the component into the `VacantEntry` and returns an `OccupiedEntry`. + /// + /// # Examples + /// + /// ``` + /// # use bevy_ecs::{prelude::*, world::Entry}; + /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] + /// struct Comp(u32); + /// + /// # let mut world = World::new(); + /// let mut entity = world.spawn_empty(); + /// + /// if let Entry::Vacant(v) = entity.entry::() { + /// v.insert_entry(Comp(10)); + /// } + /// + /// assert_eq!(world.query::<&Comp>().single(&world).0, 10); + /// ``` + #[inline] + pub fn insert_entry(self, component: T) -> OccupiedEntry<'w, 'a, T> { + self.entity_world.insert(component); + OccupiedEntry { + entity_world: self.entity_world, + _marker: PhantomData, + } + } } /// Inserts a dynamic [`Bundle`] into the entity. diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 995b503788a84..0de995257fc4d 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -7,7 +7,7 @@ pub mod unsafe_world_cell; mod world_cell; pub use crate::change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD}; -pub use entity_ref::{EntityMut, EntityRef, EntityWorldMut}; +pub use entity_ref::{EntityMut, EntityRef, EntityWorldMut, Entry, OccupiedEntry, VacantEntry}; pub use spawn_batch::*; pub use world_cell::*; @@ -17,7 +17,7 @@ use crate::{ change_detection::{MutUntyped, TicksMut}, component::{Component, ComponentDescriptor, ComponentId, ComponentInfo, Components, Tick}, entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation}, - event::{Event, Events}, + event::{Event, EventId, Events, SendBatchIds}, query::{DebugCheckedUnwrap, QueryEntityError, QueryState, ReadOnlyWorldQuery, WorldQuery}, removal_detection::RemovedComponentEvents, schedule::{Schedule, ScheduleLabel, Schedules}, @@ -1594,27 +1594,37 @@ impl World { } /// Sends an [`Event`]. + /// This method returns the [ID](`EventId`) of the sent `event`, + /// or [`None`] if the `event` could not be sent. #[inline] - pub fn send_event(&mut self, event: E) { - self.send_event_batch(std::iter::once(event)); + pub fn send_event(&mut self, event: E) -> Option> { + self.send_event_batch(std::iter::once(event))?.next() } /// Sends the default value of the [`Event`] of type `E`. + /// This method returns the [ID](`EventId`) of the sent `event`, + /// or [`None`] if the `event` could not be sent. #[inline] - pub fn send_event_default(&mut self) { - self.send_event_batch(std::iter::once(E::default())); + pub fn send_event_default(&mut self) -> Option> { + self.send_event(E::default()) } /// Sends a batch of [`Event`]s from an iterator. + /// This method returns the [IDs](`EventId`) of the sent `events`, + /// or [`None`] if the `event` could not be sent. #[inline] - 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 ", - std::any::type_name::() - ), - } + pub fn send_event_batch( + &mut self, + events: impl IntoIterator, + ) -> Option> { + let Some(mut events_resource) = self.get_resource_mut::>() else { + 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 ", + std::any::type_name::() + ); + return None; + }; + Some(events_resource.send_batch(events)) } /// Inserts a new resource with the given `value`. Will replace the value if it already existed. diff --git a/crates/bevy_ecs_compile_fail_tests/src/lib.rs b/crates/bevy_ecs_compile_fail_tests/src/lib.rs index e12684f7648c9..d0d1683dd6b97 100644 --- a/crates/bevy_ecs_compile_fail_tests/src/lib.rs +++ b/crates/bevy_ecs_compile_fail_tests/src/lib.rs @@ -1,3 +1 @@ -#![allow(clippy::type_complexity)] - // Nothing here, check out the integration tests diff --git a/crates/bevy_encase_derive/Cargo.toml b/crates/bevy_encase_derive/Cargo.toml index 311b11c6e6536..b6c65e2d69d4c 100644 --- a/crates/bevy_encase_derive/Cargo.toml +++ b/crates/bevy_encase_derive/Cargo.toml @@ -14,3 +14,6 @@ proc-macro = true [dependencies] bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.12.0" } encase_derive_impl = "0.6.1" + +[lints] +workspace = true diff --git a/crates/bevy_encase_derive/src/lib.rs b/crates/bevy_encase_derive/src/lib.rs index cc81b6edd52d0..d57be9f85c7c5 100644 --- a/crates/bevy_encase_derive/src/lib.rs +++ b/crates/bevy_encase_derive/src/lib.rs @@ -1,5 +1,3 @@ -#![allow(clippy::type_complexity)] - use bevy_macro_utils::BevyManifest; use encase_derive_impl::{implement, syn}; diff --git a/crates/bevy_gilrs/Cargo.toml b/crates/bevy_gilrs/Cargo.toml index 250ad5fbfd13f..b6439c42e128a 100644 --- a/crates/bevy_gilrs/Cargo.toml +++ b/crates/bevy_gilrs/Cargo.toml @@ -20,3 +20,6 @@ bevy_time = { path = "../bevy_time", version = "0.12.0" } # other gilrs = "0.10.1" thiserror = "1.0" + +[lints] +workspace = true diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index 553192810381c..7849bc1c80430 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -51,8 +51,11 @@ pub fn gilrs_event_system( GamepadConnectionEvent::new(gamepad, GamepadConnection::Connected(info)).into(), ); } - EventType::Disconnected => events - .send(GamepadConnectionEvent::new(gamepad, GamepadConnection::Disconnected).into()), + EventType::Disconnected => { + events.send( + GamepadConnectionEvent::new(gamepad, GamepadConnection::Disconnected).into(), + ); + } EventType::ButtonChanged(gilrs_button, raw_value, _) => { if let Some(button_type) = convert_button(gilrs_button) { let button = GamepadButton::new(gamepad, button_type); diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index 39172a4053c2b..46867b73a2f96 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -3,7 +3,6 @@ //! This crate is built on top of [GilRs](gilrs), a library //! that handles abstracting over platform-specific gamepad APIs. -#![allow(clippy::type_complexity)] #![warn(missing_docs)] mod converter; diff --git a/crates/bevy_gizmos/Cargo.toml b/crates/bevy_gizmos/Cargo.toml index 0917b71f97763..1f404d1bfc957 100644 --- a/crates/bevy_gizmos/Cargo.toml +++ b/crates/bevy_gizmos/Cargo.toml @@ -25,3 +25,6 @@ bevy_core = { path = "../bevy_core", version = "0.12.0" } bevy_reflect = { path = "../bevy_reflect", version = "0.12.0" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.0" } bevy_transform = { path = "../bevy_transform", version = "0.12.0" } + +[lints] +workspace = true diff --git a/crates/bevy_gizmos/src/arrows.rs b/crates/bevy_gizmos/src/arrows.rs new file mode 100644 index 0000000000000..9a1325c821302 --- /dev/null +++ b/crates/bevy_gizmos/src/arrows.rs @@ -0,0 +1,109 @@ +//! Additional [`Gizmos`] Functions -- Arrows +//! +//! Includes the implementation of [`Gizmos::arrow`] and [`Gizmos::arrow_2d`], +//! and assorted support items. + +use crate::prelude::Gizmos; +use bevy_math::{Quat, Vec2, Vec3}; +use bevy_render::color::Color; + +/// A builder returned by [`Gizmos::arrow`] and [`Gizmos::arrow_2d`] +pub struct ArrowBuilder<'a, 's> { + gizmos: &'a mut Gizmos<'s>, + start: Vec3, + end: Vec3, + color: Color, + tip_length: f32, +} + +impl ArrowBuilder<'_, '_> { + /// Change the length of the tips to be `length`. + /// The default tip length is [length of the arrow]/10. + /// + /// # Example + /// ``` + /// # use bevy_gizmos::prelude::*; + /// # use bevy_render::prelude::*; + /// # use bevy_math::prelude::*; + /// fn system(mut gizmos: Gizmos) { + /// gizmos.arrow(Vec3::ZERO, Vec3::ONE, Color::GREEN) + /// .with_tip_length(3.); + /// } + /// # bevy_ecs::system::assert_is_system(system); + /// ``` + #[doc(alias = "arrow_head_length")] + pub fn with_tip_length(&mut self, length: f32) { + self.tip_length = length; + } +} + +impl Drop for ArrowBuilder<'_, '_> { + /// Draws the arrow, by drawing lines with the stored [`Gizmos`] + fn drop(&mut self) { + // first, draw the body of the arrow + self.gizmos.line(self.start, self.end, self.color); + // now the hard part is to draw the head in a sensible way + // put us in a coordinate system where the arrow is pointing towards +x and ends at the origin + let pointing = (self.end - self.start).normalize(); + let rotation = Quat::from_rotation_arc(Vec3::X, pointing); + let tips = [ + Vec3::new(-1., 1., 0.), + Vec3::new(-1., 0., 1.), + Vec3::new(-1., -1., 0.), + Vec3::new(-1., 0., -1.), + ]; + // - extend the vectors so their length is `tip_length` + // - rotate the world so +x is facing in the same direction as the arrow + // - translate over to the tip of the arrow + let tips = tips.map(|v| rotation * (v.normalize() * self.tip_length) + self.end); + for v in tips { + // then actually draw the tips + self.gizmos.line(self.end, v, self.color); + } + } +} + +impl<'s> Gizmos<'s> { + /// Draw an arrow in 3D, from `start` to `end`. Has four tips for convienent viewing from any direction. + /// + /// This should be called for each frame the arrow needs to be rendered. + /// + /// # Example + /// ``` + /// # use bevy_gizmos::prelude::*; + /// # use bevy_render::prelude::*; + /// # use bevy_math::prelude::*; + /// fn system(mut gizmos: Gizmos) { + /// gizmos.arrow(Vec3::ZERO, Vec3::ONE, Color::GREEN); + /// } + /// # bevy_ecs::system::assert_is_system(system); + /// ``` + pub fn arrow(&mut self, start: Vec3, end: Vec3, color: Color) -> ArrowBuilder<'_, 's> { + let length = (end - start).length(); + ArrowBuilder { + gizmos: self, + start, + end, + color, + tip_length: length / 10., + } + } + + /// Draw an arrow in 2D (on the xy plane), from `start` to `end`. + /// + /// This should be called for each frame the arrow needs to be rendered. + /// + /// # Example + /// ``` + /// # use bevy_gizmos::prelude::*; + /// # use bevy_render::prelude::*; + /// # use bevy_math::prelude::*; + /// fn system(mut gizmos: Gizmos) { + /// gizmos.arrow_2d(Vec2::ZERO, Vec2::X, Color::GREEN); + /// } + /// # bevy_ecs::system::assert_is_system(system); + /// ``` + pub fn arrow_2d(&mut self, start: Vec2, end: Vec2, color: Color) -> ArrowBuilder<'_, 's> { + self.arrow(start.extend(0.), end.extend(0.), color) + } +} diff --git a/crates/bevy_gizmos/src/circles.rs b/crates/bevy_gizmos/src/circles.rs new file mode 100644 index 0000000000000..1b4948b198c1c --- /dev/null +++ b/crates/bevy_gizmos/src/circles.rs @@ -0,0 +1,145 @@ +//! Additional [`Gizmos`] Functions -- Circles +//! +//! Includes the implementation of [`Gizmos::circle`] and [`Gizmos::circle_2d`], +//! and assorted support items. + +use crate::prelude::Gizmos; +use bevy_math::{Quat, Vec2, Vec3}; +use bevy_render::color::Color; +use std::f32::consts::TAU; + +pub(crate) const DEFAULT_CIRCLE_SEGMENTS: usize = 32; + +fn circle_inner(radius: f32, segments: usize) -> impl Iterator { + (0..segments + 1).map(move |i| { + let angle = i as f32 * TAU / segments as f32; + Vec2::from(angle.sin_cos()) * radius + }) +} + +impl<'s> Gizmos<'s> { + /// Draw a circle in 3D at `position` with the flat side facing `normal`. + /// + /// This should be called for each frame the circle needs to be rendered. + /// + /// # Example + /// ``` + /// # use bevy_gizmos::prelude::*; + /// # use bevy_render::prelude::*; + /// # use bevy_math::prelude::*; + /// fn system(mut gizmos: Gizmos) { + /// gizmos.circle(Vec3::ZERO, Vec3::Z, 1., Color::GREEN); + /// + /// // Circles have 32 line-segments by default. + /// // You may want to increase this for larger circles. + /// gizmos + /// .circle(Vec3::ZERO, Vec3::Z, 5., Color::RED) + /// .segments(64); + /// } + /// # bevy_ecs::system::assert_is_system(system); + /// ``` + #[inline] + pub fn circle( + &mut self, + position: Vec3, + normal: Vec3, + radius: f32, + color: Color, + ) -> CircleBuilder<'_, 's> { + CircleBuilder { + gizmos: self, + position, + normal, + radius, + color, + segments: DEFAULT_CIRCLE_SEGMENTS, + } + } + + /// Draw a circle in 2D. + /// + /// This should be called for each frame the circle needs to be rendered. + /// + /// # Example + /// ``` + /// # use bevy_gizmos::prelude::*; + /// # use bevy_render::prelude::*; + /// # use bevy_math::prelude::*; + /// fn system(mut gizmos: Gizmos) { + /// gizmos.circle_2d(Vec2::ZERO, 1., Color::GREEN); + /// + /// // Circles have 32 line-segments by default. + /// // You may want to increase this for larger circles. + /// gizmos + /// .circle_2d(Vec2::ZERO, 5., Color::RED) + /// .segments(64); + /// } + /// # bevy_ecs::system::assert_is_system(system); + /// ``` + #[inline] + pub fn circle_2d( + &mut self, + position: Vec2, + radius: f32, + color: Color, + ) -> Circle2dBuilder<'_, 's> { + Circle2dBuilder { + gizmos: self, + position, + radius, + color, + segments: DEFAULT_CIRCLE_SEGMENTS, + } + } +} + +/// A builder returned by [`Gizmos::circle`]. +pub struct CircleBuilder<'a, 's> { + gizmos: &'a mut Gizmos<'s>, + position: Vec3, + normal: Vec3, + radius: f32, + color: Color, + segments: usize, +} + +impl CircleBuilder<'_, '_> { + /// Set the number of line-segments for this circle. + pub fn segments(mut self, segments: usize) -> Self { + self.segments = segments; + self + } +} + +impl Drop for CircleBuilder<'_, '_> { + fn drop(&mut self) { + let rotation = Quat::from_rotation_arc(Vec3::Z, self.normal); + let positions = circle_inner(self.radius, self.segments) + .map(|vec2| (self.position + rotation * vec2.extend(0.))); + self.gizmos.linestrip(positions, self.color); + } +} + +/// A builder returned by [`Gizmos::circle_2d`]. +pub struct Circle2dBuilder<'a, 's> { + gizmos: &'a mut Gizmos<'s>, + position: Vec2, + radius: f32, + color: Color, + segments: usize, +} + +impl Circle2dBuilder<'_, '_> { + /// Set the number of line-segments for this circle. + pub fn segments(mut self, segments: usize) -> Self { + self.segments = segments; + self + } +} + +impl Drop for Circle2dBuilder<'_, '_> { + fn drop(&mut self) { + let positions = circle_inner(self.radius, self.segments).map(|vec2| (vec2 + self.position)); + self.gizmos.linestrip_2d(positions, self.color); + } +} diff --git a/crates/bevy_gizmos/src/gizmos.rs b/crates/bevy_gizmos/src/gizmos.rs index 34c02ab3b98a9..dc1c998470fa3 100644 --- a/crates/bevy_gizmos/src/gizmos.rs +++ b/crates/bevy_gizmos/src/gizmos.rs @@ -2,6 +2,7 @@ use std::{f32::consts::TAU, iter}; +use crate::circles::DEFAULT_CIRCLE_SEGMENTS; use bevy_ecs::{ system::{Deferred, Resource, SystemBuffer, SystemMeta, SystemParam}, world::World, @@ -13,8 +14,6 @@ use bevy_transform::TransformPoint; type PositionItem = [f32; 3]; type ColorItem = [f32; 4]; -const DEFAULT_CIRCLE_SEGMENTS: usize = 32; - #[derive(Resource, Default)] pub(crate) struct GizmoStorage { pub list_positions: Vec, @@ -201,44 +200,6 @@ impl<'s> Gizmos<'s> { strip_colors.push([f32::NAN; 4]); } - /// Draw a circle in 3D at `position` with the flat side facing `normal`. - /// - /// This should be called for each frame the circle needs to be rendered. - /// - /// # Example - /// ``` - /// # use bevy_gizmos::prelude::*; - /// # use bevy_render::prelude::*; - /// # use bevy_math::prelude::*; - /// fn system(mut gizmos: Gizmos) { - /// gizmos.circle(Vec3::ZERO, Vec3::Z, 1., Color::GREEN); - /// - /// // Circles have 32 line-segments by default. - /// // You may want to increase this for larger circles. - /// gizmos - /// .circle(Vec3::ZERO, Vec3::Z, 5., Color::RED) - /// .segments(64); - /// } - /// # bevy_ecs::system::assert_is_system(system); - /// ``` - #[inline] - pub fn circle( - &mut self, - position: Vec3, - normal: Vec3, - radius: f32, - color: Color, - ) -> CircleBuilder<'_, 's> { - CircleBuilder { - gizmos: self, - position, - normal, - radius, - color, - segments: DEFAULT_CIRCLE_SEGMENTS, - } - } - /// Draw a wireframe sphere in 3D made out of 3 circles around the axes. /// /// This should be called for each frame the sphere needs to be rendered. @@ -466,42 +427,6 @@ impl<'s> Gizmos<'s> { self.line_gradient_2d(start, start + vector, start_color, end_color); } - /// Draw a circle in 2D. - /// - /// This should be called for each frame the circle needs to be rendered. - /// - /// # Example - /// ``` - /// # use bevy_gizmos::prelude::*; - /// # use bevy_render::prelude::*; - /// # use bevy_math::prelude::*; - /// fn system(mut gizmos: Gizmos) { - /// gizmos.circle_2d(Vec2::ZERO, 1., Color::GREEN); - /// - /// // Circles have 32 line-segments by default. - /// // You may want to increase this for larger circles. - /// gizmos - /// .circle_2d(Vec2::ZERO, 5., Color::RED) - /// .segments(64); - /// } - /// # bevy_ecs::system::assert_is_system(system); - /// ``` - #[inline] - pub fn circle_2d( - &mut self, - position: Vec2, - radius: f32, - color: Color, - ) -> Circle2dBuilder<'_, 's> { - Circle2dBuilder { - gizmos: self, - position, - radius, - color, - segments: DEFAULT_CIRCLE_SEGMENTS, - } - } - /// Draw an arc, which is a part of the circumference of a circle, in 2D. /// /// This should be called for each frame the arc needs to be rendered. @@ -603,33 +528,6 @@ impl<'s> Gizmos<'s> { } } -/// A builder returned by [`Gizmos::circle`]. -pub struct CircleBuilder<'a, 's> { - gizmos: &'a mut Gizmos<'s>, - position: Vec3, - normal: Vec3, - radius: f32, - color: Color, - segments: usize, -} - -impl CircleBuilder<'_, '_> { - /// Set the number of line-segments for this circle. - pub fn segments(mut self, segments: usize) -> Self { - self.segments = segments; - self - } -} - -impl Drop for CircleBuilder<'_, '_> { - fn drop(&mut self) { - let rotation = Quat::from_rotation_arc(Vec3::Z, self.normal); - let positions = circle_inner(self.radius, self.segments) - .map(|vec2| (self.position + rotation * vec2.extend(0.))); - self.gizmos.linestrip(positions, self.color); - } -} - /// A builder returned by [`Gizmos::sphere`]. pub struct SphereBuilder<'a, 's> { gizmos: &'a mut Gizmos<'s>, @@ -658,30 +556,6 @@ impl Drop for SphereBuilder<'_, '_> { } } -/// A builder returned by [`Gizmos::circle_2d`]. -pub struct Circle2dBuilder<'a, 's> { - gizmos: &'a mut Gizmos<'s>, - position: Vec2, - radius: f32, - color: Color, - segments: usize, -} - -impl Circle2dBuilder<'_, '_> { - /// Set the number of line-segments for this circle. - pub fn segments(mut self, segments: usize) -> Self { - self.segments = segments; - self - } -} - -impl Drop for Circle2dBuilder<'_, '_> { - fn drop(&mut self) { - let positions = circle_inner(self.radius, self.segments).map(|vec2| (vec2 + self.position)); - self.gizmos.linestrip_2d(positions, self.color); - } -} - /// A builder returned by [`Gizmos::arc_2d`]. pub struct Arc2dBuilder<'a, 's> { gizmos: &'a mut Gizmos<'s>, @@ -730,13 +604,6 @@ fn arc_inner( }) } -fn circle_inner(radius: f32, segments: usize) -> impl Iterator { - (0..segments + 1).map(move |i| { - let angle = i as f32 * TAU / segments as f32; - Vec2::from(angle.sin_cos()) * radius - }) -} - fn rect_inner(size: Vec2) -> [Vec2; 4] { let half_size = size / 2.; let tl = Vec2::new(-half_size.x, half_size.y); diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 446605b43e4f8..468951658849f 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -1,4 +1,3 @@ -#![allow(clippy::type_complexity)] #![warn(missing_docs)] //! This crate adds an immediate mode drawing api to Bevy for visual debugging. @@ -16,6 +15,8 @@ //! //! See the documentation on [`Gizmos`] for more examples. +pub mod arrows; +pub mod circles; pub mod gizmos; #[cfg(feature = "bevy_sprite")] @@ -37,7 +38,7 @@ use bevy_ecs::{ component::Component, entity::Entity, query::{ROQueryItem, Without}, - reflect::ReflectComponent, + reflect::{ReflectComponent, ReflectResource}, schedule::IntoSystemConfigs, system::{ lifetimeless::{Read, SRes}, @@ -77,7 +78,9 @@ impl Plugin for GizmoPlugin { fn build(&self, app: &mut bevy_app::App) { load_internal_asset!(app, LINE_SHADER_HANDLE, "lines.wgsl", Shader::from_wgsl); - app.add_plugins(UniformComponentPlugin::::default()) + app.register_type::() + .register_type::() + .add_plugins(UniformComponentPlugin::::default()) .init_asset::() .add_plugins(RenderAssetPlugin::::default()) .init_resource::() @@ -135,7 +138,8 @@ impl Plugin for GizmoPlugin { } /// A [`Resource`] that stores configuration for gizmos. -#[derive(Resource, Clone)] +#[derive(Resource, Clone, Reflect)] +#[reflect(Resource)] pub struct GizmoConfig { /// Set to `false` to stop drawing gizmos. /// @@ -188,7 +192,7 @@ impl Default for GizmoConfig { } /// Configuration for drawing the [`Aabb`] component on entities. -#[derive(Clone, Default)] +#[derive(Clone, Default, Reflect)] pub struct AabbGizmoConfig { /// Draws all bounding boxes in the scene when set to `true`. /// diff --git a/crates/bevy_gizmos/src/lines.wgsl b/crates/bevy_gizmos/src/lines.wgsl index 2eb6ef907aeb8..d5c9e1e1476eb 100644 --- a/crates/bevy_gizmos/src/lines.wgsl +++ b/crates/bevy_gizmos/src/lines.wgsl @@ -28,6 +28,8 @@ struct VertexOutput { @location(0) color: vec4, }; +const EPSILON: f32 = 4.88e-04; + @vertex fn vertex(vertex: VertexInput) -> VertexOutput { var positions = array, 6>( @@ -79,7 +81,6 @@ fn vertex(vertex: VertexInput) -> VertexOutput { if line_gizmo.depth_bias >= 0. { depth = clip.z * (1. - line_gizmo.depth_bias); } else { - let epsilon = 4.88e-04; // depth * (clip.w / depth)^-depth_bias. So that when -depth_bias is 1.0, this is equal to clip.w // and when equal to 0.0, it is exactly equal to depth. // the epsilon is here to prevent the depth from exceeding clip.w when -depth_bias = 1.0 @@ -87,7 +88,7 @@ fn vertex(vertex: VertexInput) -> VertexOutput { // of this value means nothing can be in front of this // The reason this uses an exponential function is that it makes it much easier for the // user to chose a value that is convenient for them - depth = clip.z * exp2(-line_gizmo.depth_bias * log2(clip.w / clip.z - epsilon)); + depth = clip.z * exp2(-line_gizmo.depth_bias * log2(clip.w / clip.z - EPSILON)); } var clip_position = vec4(clip.w * ((2. * screen) / resolution - 1.), depth, clip.w); @@ -101,8 +102,10 @@ fn clip_near_plane(a: vec4, b: vec4) -> vec4 { // Interpolate a towards b until it's at the near plane. let distance_a = a.z - a.w; let distance_b = b.z - b.w; - let t = distance_a / (distance_a - distance_b); - return a + (b - a) * t; + // Add an epsilon to the interpolator to ensure that the point is + // not just behind the clip plane due to floating-point imprecision. + let t = distance_a / (distance_a - distance_b) + EPSILON; + return mix(a, b, t); } return a; } diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index 8aa900018bd97..1454fa94e00f2 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -23,27 +23,34 @@ bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" } bevy_log = { path = "../bevy_log", version = "0.12.0" } bevy_math = { path = "../bevy_math", version = "0.12.0" } bevy_pbr = { path = "../bevy_pbr", version = "0.12.0" } -bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] } +bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [ + "bevy", +] } bevy_render = { path = "../bevy_render", version = "0.12.0" } -bevy_scene = { path = "../bevy_scene", version = "0.12.0", features = ["bevy_render"] } +bevy_scene = { path = "../bevy_scene", version = "0.12.0", features = [ + "bevy_render", +] } bevy_transform = { path = "../bevy_transform", version = "0.12.0" } bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" } bevy_utils = { path = "../bevy_utils", version = "0.12.0" } # other gltf = { version = "1.3.0", default-features = false, features = [ - "KHR_lights_punctual", - "KHR_materials_transmission", - "KHR_materials_ior", - "KHR_materials_volume", - "KHR_materials_unlit", - "KHR_materials_emissive_strength", - "extras", - "names", - "utils", + "KHR_lights_punctual", + "KHR_materials_transmission", + "KHR_materials_ior", + "KHR_materials_volume", + "KHR_materials_unlit", + "KHR_materials_emissive_strength", + "extras", + "names", + "utils", ] } thiserror = "1.0" base64 = "0.13.0" percent-encoding = "2.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1" + +[lints] +workspace = true diff --git a/crates/bevy_gltf/src/lib.rs b/crates/bevy_gltf/src/lib.rs index 559c8ae3c8e63..fe47fd4dfdd9f 100644 --- a/crates/bevy_gltf/src/lib.rs +++ b/crates/bevy_gltf/src/lib.rs @@ -2,7 +2,7 @@ //! for loading glTF 2.0 (a standard 3D scene definition format) files in Bevy. //! //! The [glTF 2.0 specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html) defines the format of the glTF files. -#![allow(clippy::type_complexity)] + #![warn(missing_docs)] #[cfg(feature = "bevy_animation")] diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index abdbc1a3cbe50..bc1f3c3615a33 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -998,7 +998,7 @@ fn load_node( gltf::khr_lights_punctual::Kind::Directional => { let mut entity = parent.spawn(DirectionalLightBundle { directional_light: DirectionalLight { - color: Color::from(light.color()), + color: Color::rgb_from_array(light.color()), // NOTE: KHR_punctual_lights defines the intensity units for directional // lights in lux (lm/m^2) which is what we need. illuminance: light.intensity(), @@ -1018,7 +1018,7 @@ fn load_node( gltf::khr_lights_punctual::Kind::Point => { let mut entity = parent.spawn(PointLightBundle { point_light: PointLight { - color: Color::from(light.color()), + color: Color::rgb_from_array(light.color()), // NOTE: KHR_punctual_lights defines the intensity units for point lights in // candela (lm/sr) which is luminous intensity and we need luminous power. // For a point light, luminous power = 4 * pi * luminous intensity @@ -1044,7 +1044,7 @@ fn load_node( } => { let mut entity = parent.spawn(SpotLightBundle { spot_light: SpotLight { - color: Color::from(light.color()), + color: Color::rgb_from_array(light.color()), // NOTE: KHR_punctual_lights defines the intensity units for spot lights in // candela (lm/sr) which is luminous intensity and we need luminous power. // For a spot light, we map luminous power = 4 * pi * luminous intensity diff --git a/crates/bevy_hierarchy/Cargo.toml b/crates/bevy_hierarchy/Cargo.toml index 20c6b86811f85..03b7dadf64ba9 100644 --- a/crates/bevy_hierarchy/Cargo.toml +++ b/crates/bevy_hierarchy/Cargo.toml @@ -15,10 +15,17 @@ trace = [] # bevy bevy_app = { path = "../bevy_app", version = "0.12.0" } bevy_core = { path = "../bevy_core", version = "0.12.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.12.0", features = ["bevy_reflect"] } +bevy_ecs = { path = "../bevy_ecs", version = "0.12.0", features = [ + "bevy_reflect", +] } bevy_log = { path = "../bevy_log", version = "0.12.0" } -bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] } +bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [ + "bevy", +] } bevy_utils = { path = "../bevy_utils", version = "0.12.0" } # other smallvec = { version = "1.6", features = ["serde", "union", "const_generics"] } + +[lints] +workspace = true diff --git a/crates/bevy_hierarchy/src/child_builder.rs b/crates/bevy_hierarchy/src/child_builder.rs index 9149006219cf6..801620430ad58 100644 --- a/crates/bevy_hierarchy/src/child_builder.rs +++ b/crates/bevy_hierarchy/src/child_builder.rs @@ -253,6 +253,27 @@ impl Command for RemoveParent { } /// Struct for building children entities and adding them to a parent entity. +/// +/// # Example +/// +/// This example creates three entities, a parent and two children. +/// +/// ``` +/// # use bevy_ecs::bundle::Bundle; +/// # use bevy_ecs::system::Commands; +/// # use bevy_hierarchy::BuildChildren; +/// # #[derive(Bundle)] +/// # struct MyBundle {} +/// # #[derive(Bundle)] +/// # struct MyChildBundle {} +/// # +/// # fn test(mut commands: Commands) { +/// commands.spawn(MyBundle {}).with_children(|child_builder| { +/// child_builder.spawn(MyChildBundle {}); +/// child_builder.spawn(MyChildBundle {}); +/// }); +/// # } +/// ``` pub struct ChildBuilder<'w, 's, 'a> { commands: &'a mut Commands<'w, 's>, push_children: PushChildren, diff --git a/crates/bevy_hierarchy/src/components/children.rs b/crates/bevy_hierarchy/src/components/children.rs index 02d9e0e388329..73a7aad7fbc76 100644 --- a/crates/bevy_hierarchy/src/components/children.rs +++ b/crates/bevy_hierarchy/src/components/children.rs @@ -12,10 +12,17 @@ use std::ops::Deref; /// Contains references to the child entities of this entity. /// +/// Each child must contain a [`Parent`] component that points back to this entity. +/// This component rarely needs to be created manually, +/// consider using higher level utilities like [`BuildChildren::with_children`] +/// which are safer and easier to use. +/// /// See [`HierarchyQueryExt`] for hierarchy related methods on [`Query`]. /// /// [`HierarchyQueryExt`]: crate::query_extension::HierarchyQueryExt /// [`Query`]: bevy_ecs::system::Query +/// [`Parent`]: crate::components::parent::Parent +/// [`BuildChildren::with_children`]: crate::child_builder::BuildChildren::with_children #[derive(Component, Debug, Reflect)] #[reflect(Component, MapEntities)] pub struct Children(pub(crate) SmallVec<[Entity; 8]>); diff --git a/crates/bevy_hierarchy/src/components/parent.rs b/crates/bevy_hierarchy/src/components/parent.rs index 2a013ad8a142d..e9616391afb08 100644 --- a/crates/bevy_hierarchy/src/components/parent.rs +++ b/crates/bevy_hierarchy/src/components/parent.rs @@ -10,10 +10,16 @@ use std::ops::Deref; /// Holds a reference to the parent entity of this entity. /// This component should only be present on entities that actually have a parent entity. /// +/// Parent entity must have this entity stored in its [`Children`] component. +/// It is hard to set up parent/child relationships manually, +/// consider using higher level utilities like [`BuildChildren::with_children`]. +/// /// See [`HierarchyQueryExt`] for hierarchy related methods on [`Query`]. /// /// [`HierarchyQueryExt`]: crate::query_extension::HierarchyQueryExt /// [`Query`]: bevy_ecs::system::Query +/// [`Children`]: super::children::Children +/// [`BuildChildren::with_children`]: crate::child_builder::BuildChildren::with_children #[derive(Component, Debug, Eq, PartialEq, Reflect)] #[reflect(Component, MapEntities, PartialEq)] pub struct Parent(pub(crate) Entity); diff --git a/crates/bevy_hierarchy/src/lib.rs b/crates/bevy_hierarchy/src/lib.rs index d616e5384ae13..f156086b1a926 100644 --- a/crates/bevy_hierarchy/src/lib.rs +++ b/crates/bevy_hierarchy/src/lib.rs @@ -1,4 +1,3 @@ -#![allow(clippy::type_complexity)] #![warn(missing_docs)] //! `bevy_hierarchy` can be used to define hierarchies of entities. //! diff --git a/crates/bevy_input/Cargo.toml b/crates/bevy_input/Cargo.toml index 53915229662d7..555aecd409a75 100644 --- a/crates/bevy_input/Cargo.toml +++ b/crates/bevy_input/Cargo.toml @@ -19,7 +19,7 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" } bevy_math = { path = "../bevy_math", version = "0.12.0" } bevy_utils = { path = "../bevy_utils", version = "0.12.0" } bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [ - "glam", + "glam", ] } # other @@ -29,3 +29,6 @@ smol_str = "0.2" [dev-dependencies] bevy = { path = "../../", version = "0.12.0" } + +[lints] +workspace = true diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index 96ab125fa2907..338458605e292 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -516,14 +516,14 @@ impl ButtonSettings { /// Returns `true` if the button is pressed. /// /// A button is considered pressed if the `value` passed is greater than or equal to the press threshold. - fn is_pressed(&self, value: f32) -> bool { + pub fn is_pressed(&self, value: f32) -> bool { value >= self.press_threshold } /// Returns `true` if the button is released. /// /// A button is considered released if the `value` passed is lower than or equal to the release threshold. - fn is_released(&self, value: f32) -> bool { + pub fn is_released(&self, value: f32) -> bool { value <= self.release_threshold } @@ -1263,8 +1263,12 @@ pub fn gamepad_event_system( GamepadEvent::Connection(connection_event) => { connection_events.send(connection_event.clone()); } - GamepadEvent::Button(button_event) => button_events.send(button_event.clone()), - GamepadEvent::Axis(axis_event) => axis_events.send(axis_event.clone()), + GamepadEvent::Button(button_event) => { + button_events.send(button_event.clone()); + } + GamepadEvent::Axis(axis_event) => { + axis_events.send(axis_event.clone()); + } } } } diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index 1539e02c36aa4..09077be6b1e18 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -1,4 +1,3 @@ -#![allow(clippy::type_complexity)] #![warn(missing_docs)] //! Input functionality for the [Bevy game engine](https://bevyengine.org/). diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index a9c46dd06a615..0563bb6a2122f 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -11,16 +11,16 @@ categories = ["game-engines", "graphics", "gui", "rendering"] [features] trace = [ - "bevy_app/trace", - "bevy_core_pipeline?/trace", - "bevy_ecs/trace", - "bevy_log/trace", - "bevy_render?/trace", - "bevy_hierarchy/trace", - "bevy_winit?/trace" + "bevy_app/trace", + "bevy_core_pipeline?/trace", + "bevy_ecs/trace", + "bevy_log/trace", + "bevy_render?/trace", + "bevy_hierarchy/trace", + "bevy_winit?/trace", ] -trace_chrome = [ "bevy_log/tracing-chrome" ] -trace_tracy = ["bevy_render?/tracing-tracy", "bevy_log/tracing-tracy" ] +trace_chrome = ["bevy_log/tracing-chrome"] +trace_tracy = ["bevy_render?/tracing-tracy", "bevy_log/tracing-tracy"] trace_tracy_memory = ["bevy_log/trace_tracy_memory"] wgpu_trace = ["bevy_render/wgpu_trace"] detailed_trace = ["bevy_utils/detailed_trace"] @@ -61,8 +61,20 @@ symphonia-wav = ["bevy_audio/symphonia-wav"] shader_format_glsl = ["bevy_render/shader_format_glsl"] shader_format_spirv = ["bevy_render/shader_format_spirv"] -serialize = ["bevy_core/serialize", "bevy_input/serialize", "bevy_time/serialize", "bevy_window/serialize", "bevy_transform/serialize", "bevy_math/serialize", "bevy_scene?/serialize"] -multi-threaded = ["bevy_asset/multi-threaded", "bevy_ecs/multi-threaded", "bevy_tasks/multi-threaded"] +serialize = [ + "bevy_core/serialize", + "bevy_input/serialize", + "bevy_time/serialize", + "bevy_window/serialize", + "bevy_transform/serialize", + "bevy_math/serialize", + "bevy_scene?/serialize", +] +multi-threaded = [ + "bevy_asset/multi-threaded", + "bevy_ecs/multi-threaded", + "bevy_tasks/multi-threaded", +] async-io = ["bevy_tasks/async-io"] # Display server protocol support (X11 is enabled by default) @@ -73,13 +85,27 @@ x11 = ["bevy_winit/x11"] subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"] # Transmission textures in `StandardMaterial`: -pbr_transmission_textures = ["bevy_pbr?/pbr_transmission_textures", "bevy_gltf?/pbr_transmission_textures"] +pbr_transmission_textures = [ + "bevy_pbr?/pbr_transmission_textures", + "bevy_gltf?/pbr_transmission_textures", +] # Optimise for WebGL2 -webgl = ["bevy_core_pipeline?/webgl", "bevy_pbr?/webgl", "bevy_render?/webgl", "bevy_gizmos?/webgl", "bevy_sprite?/webgl"] +webgl = [ + "bevy_core_pipeline?/webgl", + "bevy_pbr?/webgl", + "bevy_render?/webgl", + "bevy_gizmos?/webgl", + "bevy_sprite?/webgl", +] # enable systems that allow for automated testing on CI -bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_time/bevy_ci_testing", "bevy_render?/bevy_ci_testing", "bevy_render?/ci_limits"] +bevy_ci_testing = [ + "bevy_app/bevy_ci_testing", + "bevy_time/bevy_ci_testing", + "bevy_render?/bevy_ci_testing", + "bevy_render?/ci_limits", +] # Enable animation support, and glTF animation loading animation = ["bevy_animation", "bevy_gltf?/bevy_animation"] @@ -131,7 +157,9 @@ bevy_input = { path = "../bevy_input", version = "0.12.0" } bevy_log = { path = "../bevy_log", version = "0.12.0" } bevy_math = { path = "../bevy_math", version = "0.12.0" } bevy_ptr = { path = "../bevy_ptr", version = "0.12.0" } -bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] } +bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [ + "bevy", +] } bevy_time = { path = "../bevy_time", version = "0.12.0" } bevy_transform = { path = "../bevy_transform", version = "0.12.0" } bevy_utils = { path = "../bevy_utils", version = "0.12.0" } @@ -153,3 +181,6 @@ bevy_ui = { path = "../bevy_ui", optional = true, version = "0.12.0" } bevy_winit = { path = "../bevy_winit", optional = true, version = "0.12.0" } bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.12.0" } bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.12.0", default-features = false } + +[lints] +workspace = true diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index 8e6bf57e52909..42b368375a6e1 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -1,4 +1,3 @@ -#![allow(clippy::type_complexity)] #![warn(missing_docs)] //! This module is separated into its own crate to enable simple dynamic linking for Bevy, and should not be used directly diff --git a/crates/bevy_log/Cargo.toml b/crates/bevy_log/Cargo.toml index 4075942ae4403..9e9344d0583c3 100644 --- a/crates/bevy_log/Cargo.toml +++ b/crates/bevy_log/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [features] -trace = [ "tracing-error" ] +trace = ["tracing-error"] trace_tracy_memory = ["dep:tracy-client"] [dependencies] @@ -17,7 +17,10 @@ bevy_app = { path = "../bevy_app", version = "0.12.0" } bevy_utils = { path = "../bevy_utils", version = "0.12.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" } -tracing-subscriber = {version = "0.3.1", features = ["registry", "env-filter"]} +tracing-subscriber = { version = "0.3.1", features = [ + "registry", + "env-filter", +] } tracing-chrome = { version = "0.7.0", optional = true } tracing-tracy = { version = "0.10.0", optional = true } tracing-log = "0.1.2" @@ -30,3 +33,6 @@ android_log-sys = "0.3.0" [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook = "0.1.6" tracing-wasm = "0.2.1" + +[lints] +workspace = true diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index a912d7d984b5c..cc8d3e7a47100 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -1,4 +1,3 @@ -#![allow(clippy::type_complexity)] #![warn(missing_docs)] //! This crate provides logging functions and configuration for [Bevy](https://bevyengine.org) //! apps, and automatically configures platform specific log handlers (i.e. WASM or Android). diff --git a/crates/bevy_macro_utils/Cargo.toml b/crates/bevy_macro_utils/Cargo.toml index b790c8c9fae8e..88082b5a1ecec 100644 --- a/crates/bevy_macro_utils/Cargo.toml +++ b/crates/bevy_macro_utils/Cargo.toml @@ -14,3 +14,6 @@ syn = "2.0" quote = "1.0" rustc-hash = "1.0" proc-macro2 = "1.0" + +[lints] +workspace = true diff --git a/crates/bevy_macro_utils/src/lib.rs b/crates/bevy_macro_utils/src/lib.rs index 2363d61cf0062..d14a2c6db7eef 100644 --- a/crates/bevy_macro_utils/src/lib.rs +++ b/crates/bevy_macro_utils/src/lib.rs @@ -1,4 +1,3 @@ -#![allow(clippy::type_complexity)] #![warn(missing_docs)] #![deny(unsafe_code)] //! A collection of helper types and functions for working on macros within the Bevy ecosystem. diff --git a/crates/bevy_macro_utils/src/shape.rs b/crates/bevy_macro_utils/src/shape.rs index 36ce443bf4396..30eee9a7acbe2 100644 --- a/crates/bevy_macro_utils/src/shape.rs +++ b/crates/bevy_macro_utils/src/shape.rs @@ -1,20 +1,24 @@ use proc_macro::Span; -use syn::{Data, DataStruct, Error, Fields, FieldsNamed}; +use syn::{punctuated::Punctuated, token::Comma, Data, DataStruct, Error, Field, Fields}; /// Get the fields of a data structure if that structure is a struct with named fields; /// otherwise, return a compile error that points to the site of the macro invocation. -pub fn get_named_struct_fields(data: &syn::Data) -> syn::Result<&FieldsNamed> { +pub fn get_struct_fields(data: &syn::Data) -> syn::Result<&Punctuated> { match data { Data::Struct(DataStruct { fields: Fields::Named(fields), .. - }) => Ok(fields), + }) => Ok(&fields.named), + Data::Struct(DataStruct { + fields: Fields::Unnamed(fields), + .. + }) => Ok(&fields.unnamed), _ => Err(Error::new( // This deliberately points to the call site rather than the structure // body; marking the entire body as the source of the error makes it // impossible to figure out which `derive` has a problem. Span::call_site().into(), - "Only structs with named fields are supported", + "Only structs are supported", )), } } diff --git a/crates/bevy_macros_compile_fail_tests/Cargo.toml b/crates/bevy_macros_compile_fail_tests/Cargo.toml index e5d0d19bec361..3b905610662f2 100644 --- a/crates/bevy_macros_compile_fail_tests/Cargo.toml +++ b/crates/bevy_macros_compile_fail_tests/Cargo.toml @@ -10,4 +10,4 @@ publish = false [dependencies] bevy_derive = { path = "../bevy_derive" } -trybuild = "1.0.71" \ No newline at end of file +trybuild = "1.0.71" diff --git a/crates/bevy_math/Cargo.toml b/crates/bevy_math/Cargo.toml index 09daa7d938792..7ee418a42158e 100644 --- a/crates/bevy_math/Cargo.toml +++ b/crates/bevy_math/Cargo.toml @@ -20,3 +20,6 @@ mint = ["glam/mint"] glam_assert = ["glam/glam-assert"] # Enable assertions in debug builds to check the validity of parameters passed to glam debug_glam_assert = ["glam/debug-glam-assert"] + +[lints] +workspace = true diff --git a/crates/bevy_math/src/lib.rs b/crates/bevy_math/src/lib.rs index 106f0ba08d64c..4d44d095c8fed 100644 --- a/crates/bevy_math/src/lib.rs +++ b/crates/bevy_math/src/lib.rs @@ -4,11 +4,11 @@ //! matrices like [`Mat2`], [`Mat3`] and [`Mat4`] and orientation representations //! like [`Quat`]. -#![allow(clippy::type_complexity)] #![warn(missing_docs)] mod affine3; pub mod cubic_splines; +pub mod primitives; mod ray; mod rects; @@ -24,8 +24,8 @@ pub mod prelude { CubicBSpline, CubicBezier, CubicCardinalSpline, CubicGenerator, CubicHermite, CubicSegment, }, - BVec2, BVec3, BVec4, EulerRot, IRect, IVec2, IVec3, IVec4, Mat2, Mat3, Mat4, Quat, Ray, - Rect, URect, UVec2, UVec3, UVec4, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, + primitives, BVec2, BVec3, BVec4, EulerRot, IRect, IVec2, IVec3, IVec4, Mat2, Mat3, Mat4, + Quat, Ray, Rect, URect, UVec2, UVec3, UVec4, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles, }; } diff --git a/crates/bevy_math/src/primitives/dim2.rs b/crates/bevy_math/src/primitives/dim2.rs new file mode 100644 index 0000000000000..35e5363dc6acd --- /dev/null +++ b/crates/bevy_math/src/primitives/dim2.rs @@ -0,0 +1,354 @@ +use super::{Primitive2d, WindingOrder}; +use crate::Vec2; + +/// A normalized vector pointing in a direction in 2D space +#[derive(Clone, Copy, Debug)] +pub struct Direction2d(Vec2); + +impl From for Direction2d { + fn from(value: Vec2) -> Self { + Self(value.normalize()) + } +} + +impl Direction2d { + /// Create a direction from a [`Vec2`] that is already normalized + pub fn from_normalized(value: Vec2) -> Self { + debug_assert!(value.is_normalized()); + Self(value) + } +} + +impl std::ops::Deref for Direction2d { + type Target = Vec2; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// A circle primitive +#[derive(Clone, Copy, Debug)] +pub struct Circle { + /// The radius of the circle + pub radius: f32, +} +impl Primitive2d for Circle {} + +/// An ellipse primitive +#[derive(Clone, Copy, Debug)] +pub struct Ellipse { + /// The half "width" of the ellipse + pub half_width: f32, + /// The half "height" of the ellipse + pub half_height: f32, +} +impl Primitive2d for Ellipse {} + +impl Ellipse { + /// Create a new `Ellipse` from a "width" and a "height" + pub fn new(width: f32, height: f32) -> Self { + Self { + half_width: width / 2.0, + half_height: height / 2.0, + } + } +} + +/// An unbounded plane in 2D space. It forms a separating surface through the origin, +/// stretching infinitely far +#[derive(Clone, Copy, Debug)] +pub struct Plane2d { + /// The normal of the plane. The plane will be placed perpendicular to this direction + pub normal: Direction2d, +} +impl Primitive2d for Plane2d {} + +/// An infinite line along a direction in 2D space. +/// +/// For a finite line: [`Segment2d`] +#[derive(Clone, Copy, Debug)] +pub struct Line2d { + /// The direction of the line. The line extends infinitely in both the given direction + /// and its opposite direction + pub direction: Direction2d, +} +impl Primitive2d for Line2d {} + +/// A segment of a line along a direction in 2D space. +#[doc(alias = "LineSegment2d")] +#[derive(Clone, Debug)] +pub struct Segment2d { + /// The direction of the line segment + pub direction: Direction2d, + /// Half the length of the line segment. The segment extends by this amount in both + /// the given direction and its opposite direction + pub half_length: f32, +} +impl Primitive2d for Segment2d {} + +impl Segment2d { + /// Create a line segment from a direction and full length of the segment + pub fn new(direction: Direction2d, length: f32) -> Self { + Self { + direction, + half_length: length / 2., + } + } + + /// Get a line segment and translation from two points at each end of a line segment + /// + /// Panics if point1 == point2 + pub fn from_points(point1: Vec2, point2: Vec2) -> (Self, Vec2) { + let diff = point2 - point1; + let length = diff.length(); + ( + Self::new(Direction2d::from_normalized(diff / length), length), + (point1 + point2) / 2., + ) + } + + /// Get the position of the first point on the line segment + pub fn point1(&self) -> Vec2 { + *self.direction * -self.half_length + } + + /// Get the position of the second point on the line segment + pub fn point2(&self) -> Vec2 { + *self.direction * self.half_length + } +} + +/// A series of connected line segments in 2D space. +/// +/// For a version without generics: [`BoxedPolyline2d`] +#[derive(Clone, Debug)] +pub struct Polyline2d { + /// The vertices of the polyline + pub vertices: [Vec2; N], +} +impl Primitive2d for Polyline2d {} + +impl FromIterator for Polyline2d { + fn from_iter>(iter: I) -> Self { + let mut vertices: [Vec2; N] = [Vec2::ZERO; N]; + + for (index, i) in iter.into_iter().take(N).enumerate() { + vertices[index] = i; + } + Self { vertices } + } +} + +impl Polyline2d { + /// Create a new `Polyline2d` from its vertices + pub fn new(vertices: impl IntoIterator) -> Self { + Self::from_iter(vertices) + } +} + +/// A series of connected line segments in 2D space, allocated on the heap +/// in a `Box<[Vec2]>`. +/// +/// For a version without alloc: [`Polyline2d`] +#[derive(Clone, Debug)] +pub struct BoxedPolyline2d { + /// The vertices of the polyline + pub vertices: Box<[Vec2]>, +} +impl Primitive2d for BoxedPolyline2d {} + +impl FromIterator for BoxedPolyline2d { + fn from_iter>(iter: I) -> Self { + let vertices: Vec = iter.into_iter().collect(); + Self { + vertices: vertices.into_boxed_slice(), + } + } +} + +impl BoxedPolyline2d { + /// Create a new `BoxedPolyline2d` from its vertices + pub fn new(vertices: impl IntoIterator) -> Self { + Self::from_iter(vertices) + } +} + +/// A triangle in 2D space +#[derive(Clone, Debug, PartialEq)] +pub struct Triangle2d { + /// The vertices of the triangle + pub vertices: [Vec2; 3], +} +impl Primitive2d for Triangle2d {} + +impl Triangle2d { + /// Create a new `Triangle2d` from points `a`, `b`, and `c` + pub fn new(a: Vec2, b: Vec2, c: Vec2) -> Self { + Self { + vertices: [a, b, c], + } + } + + /// Get the [`WindingOrder`] of the triangle + #[doc(alias = "orientation")] + pub fn winding_order(&self) -> WindingOrder { + let [a, b, c] = self.vertices; + let area = (b - a).perp_dot(c - a); + if area > f32::EPSILON { + WindingOrder::CounterClockwise + } else if area < -f32::EPSILON { + WindingOrder::Clockwise + } else { + WindingOrder::Invalid + } + } + + /// Reverse the [`WindingOrder`] of the triangle + /// by swapping the second and third vertices + pub fn reverse(&mut self) { + self.vertices.swap(1, 2); + } +} + +/// A rectangle primitive +#[doc(alias = "Quad")] +#[derive(Clone, Copy, Debug)] +pub struct Rectangle { + /// The half width of the rectangle + pub half_width: f32, + /// The half height of the rectangle + pub half_height: f32, +} +impl Primitive2d for Rectangle {} + +impl Rectangle { + /// Create a rectangle from a full width and height + pub fn new(width: f32, height: f32) -> Self { + Self::from_size(Vec2::new(width, height)) + } + + /// Create a rectangle from a given full size + pub fn from_size(size: Vec2) -> Self { + Self { + half_width: size.x / 2., + half_height: size.y / 2., + } + } +} + +/// A polygon with N vertices. +/// +/// For a version without generics: [`BoxedPolygon`] +#[derive(Clone, Debug)] +pub struct Polygon { + /// The vertices of the `Polygon` + pub vertices: [Vec2; N], +} +impl Primitive2d for Polygon {} + +impl FromIterator for Polygon { + fn from_iter>(iter: I) -> Self { + let mut vertices: [Vec2; N] = [Vec2::ZERO; N]; + + for (index, i) in iter.into_iter().take(N).enumerate() { + vertices[index] = i; + } + Self { vertices } + } +} + +impl Polygon { + /// Create a new `Polygon` from its vertices + pub fn new(vertices: impl IntoIterator) -> Self { + Self::from_iter(vertices) + } +} + +/// A polygon with a variable number of vertices, allocated on the heap +/// in a `Box<[Vec2]>`. +/// +/// For a version without alloc: [`Polygon`] +#[derive(Clone, Debug)] +pub struct BoxedPolygon { + /// The vertices of the `BoxedPolygon` + pub vertices: Box<[Vec2]>, +} +impl Primitive2d for BoxedPolygon {} + +impl FromIterator for BoxedPolygon { + fn from_iter>(iter: I) -> Self { + let vertices: Vec = iter.into_iter().collect(); + Self { + vertices: vertices.into_boxed_slice(), + } + } +} + +impl BoxedPolygon { + /// Create a new `BoxedPolygon` from its vertices + pub fn new(vertices: impl IntoIterator) -> Self { + Self::from_iter(vertices) + } +} + +/// A polygon where all vertices lie on a circle, equally far apart +#[derive(Clone, Copy, Debug)] +pub struct RegularPolygon { + /// The circumcircle on which all vertices lie + pub circumcircle: Circle, + /// The number of sides + pub sides: usize, +} +impl Primitive2d for RegularPolygon {} + +impl RegularPolygon { + /// Create a new `RegularPolygon` + /// from the radius of the circumcircle and number of sides + /// + /// # Panics + /// + /// Panics if `circumcircle_radius` is non-positive + pub fn new(circumcircle_radius: f32, sides: usize) -> Self { + assert!(circumcircle_radius > 0.0); + Self { + circumcircle: Circle { + radius: circumcircle_radius, + }, + sides, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn triangle_winding_order() { + let mut cw_triangle = Triangle2d::new( + Vec2::new(0.0, 2.0), + Vec2::new(-0.5, -1.2), + Vec2::new(-1.0, -1.0), + ); + assert_eq!(cw_triangle.winding_order(), WindingOrder::Clockwise); + + let ccw_triangle = Triangle2d::new( + Vec2::new(0.0, 2.0), + Vec2::new(-1.0, -1.0), + Vec2::new(-0.5, -1.2), + ); + assert_eq!(ccw_triangle.winding_order(), WindingOrder::CounterClockwise); + + // The clockwise triangle should be the same as the counterclockwise + // triangle when reversed + cw_triangle.reverse(); + assert_eq!(cw_triangle, ccw_triangle); + + let invalid_triangle = Triangle2d::new( + Vec2::new(0.0, 2.0), + Vec2::new(0.0, -1.0), + Vec2::new(0.0, -1.2), + ); + assert_eq!(invalid_triangle.winding_order(), WindingOrder::Invalid); + } +} diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs new file mode 100644 index 0000000000000..891f51558fd3b --- /dev/null +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -0,0 +1,333 @@ +use super::Primitive3d; +use crate::Vec3; + +/// A normalized vector pointing in a direction in 3D space +#[derive(Clone, Copy, Debug)] +pub struct Direction3d(Vec3); + +impl From for Direction3d { + fn from(value: Vec3) -> Self { + Self(value.normalize()) + } +} + +impl Direction3d { + /// Create a direction from a [`Vec3`] that is already normalized + pub fn from_normalized(value: Vec3) -> Self { + debug_assert!(value.is_normalized()); + Self(value) + } +} + +impl std::ops::Deref for Direction3d { + type Target = Vec3; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// A sphere primitive +#[derive(Clone, Copy, Debug)] +pub struct Sphere { + /// The radius of the sphere + pub radius: f32, +} +impl Primitive3d for Sphere {} + +/// An unbounded plane in 3D space. It forms a separating surface through the origin, +/// stretching infinitely far +#[derive(Clone, Copy, Debug)] +pub struct Plane3d { + /// The normal of the plane. The plane will be placed perpendicular to this direction + pub normal: Direction3d, +} +impl Primitive3d for Plane3d {} + +/// An infinite line along a direction in 3D space. +/// +/// For a finite line: [`Segment3d`] +#[derive(Clone, Copy, Debug)] +pub struct Line3d { + /// The direction of the line + pub direction: Direction3d, +} +impl Primitive3d for Line3d {} + +/// A segment of a line along a direction in 3D space. +#[doc(alias = "LineSegment3d")] +#[derive(Clone, Debug)] +pub struct Segment3d { + /// The direction of the line + pub direction: Direction3d, + /// Half the length of the line segment. The segment extends by this amount in both + /// the given direction and its opposite direction + pub half_length: f32, +} +impl Primitive3d for Segment3d {} + +impl Segment3d { + /// Create a line segment from a direction and full length of the segment + pub fn new(direction: Direction3d, length: f32) -> Self { + Self { + direction, + half_length: length / 2., + } + } + + /// Get a line segment and translation from two points at each end of a line segment + /// + /// Panics if point1 == point2 + pub fn from_points(point1: Vec3, point2: Vec3) -> (Self, Vec3) { + let diff = point2 - point1; + let length = diff.length(); + ( + Self::new(Direction3d::from_normalized(diff / length), length), + (point1 + point2) / 2., + ) + } + + /// Get the position of the first point on the line segment + pub fn point1(&self) -> Vec3 { + *self.direction * -self.half_length + } + + /// Get the position of the second point on the line segment + pub fn point2(&self) -> Vec3 { + *self.direction * self.half_length + } +} + +/// A series of connected line segments in 3D space. +/// +/// For a version without generics: [`BoxedPolyline3d`] +#[derive(Clone, Debug)] +pub struct Polyline3d { + /// The vertices of the polyline + pub vertices: [Vec3; N], +} +impl Primitive3d for Polyline3d {} + +impl FromIterator for Polyline3d { + fn from_iter>(iter: I) -> Self { + let mut vertices: [Vec3; N] = [Vec3::ZERO; N]; + + for (index, i) in iter.into_iter().take(N).enumerate() { + vertices[index] = i; + } + Self { vertices } + } +} + +impl Polyline3d { + /// Create a new `Polyline3d` from its vertices + pub fn new(vertices: impl IntoIterator) -> Self { + Self::from_iter(vertices) + } +} + +/// A series of connected line segments in 3D space, allocated on the heap +/// in a `Box<[Vec3]>`. +/// +/// For a version without alloc: [`Polyline3d`] +#[derive(Clone, Debug)] +pub struct BoxedPolyline3d { + /// The vertices of the polyline + pub vertices: Box<[Vec3]>, +} +impl Primitive3d for BoxedPolyline3d {} + +impl FromIterator for BoxedPolyline3d { + fn from_iter>(iter: I) -> Self { + let vertices: Vec = iter.into_iter().collect(); + Self { + vertices: vertices.into_boxed_slice(), + } + } +} + +impl BoxedPolyline3d { + /// Create a new `BoxedPolyline3d` from its vertices + pub fn new(vertices: impl IntoIterator) -> Self { + Self::from_iter(vertices) + } +} + +/// A cuboid primitive, more commonly known as a box. +#[derive(Clone, Copy, Debug)] +pub struct Cuboid { + /// Half of the width, height and depth of the cuboid + pub half_extents: Vec3, +} +impl Primitive3d for Cuboid {} + +impl Cuboid { + /// Create a cuboid from a full x, y, and z length + pub fn new(x_length: f32, y_length: f32, z_length: f32) -> Self { + Self::from_size(Vec3::new(x_length, y_length, z_length)) + } + + /// Create a cuboid from a given full size + pub fn from_size(size: Vec3) -> Self { + Self { + half_extents: size / 2., + } + } +} + +/// A cylinder primitive +#[derive(Clone, Copy, Debug)] +pub struct Cylinder { + /// The radius of the cylinder + pub radius: f32, + /// The half height of the cylinder + pub half_height: f32, +} +impl Primitive3d for Cylinder {} + +impl Cylinder { + /// Create a cylinder from a radius and full height + pub fn new(radius: f32, height: f32) -> Self { + Self { + radius, + half_height: height / 2., + } + } +} + +/// A capsule primitive. +/// A capsule is defined as a surface at a distance (radius) from a line +#[derive(Clone, Copy, Debug)] +pub struct Capsule { + /// The radius of the capsule + pub radius: f32, + /// Half the height of the capsule, excluding the hemispheres + pub half_length: f32, +} +impl super::Primitive2d for Capsule {} +impl Primitive3d for Capsule {} + +impl Capsule { + /// Create a new `Capsule` from a radius and length + pub fn new(radius: f32, length: f32) -> Self { + Self { + radius, + half_length: length / 2.0, + } + } +} + +/// A cone primitive. +#[derive(Clone, Copy, Debug)] +pub struct Cone { + /// The radius of the base + pub radius: f32, + /// The height of the cone + pub height: f32, +} +impl Primitive3d for Cone {} + +/// A conical frustum primitive. +/// A conical frustum can be created +/// by slicing off a section of a cone. +#[derive(Clone, Copy, Debug)] +pub struct ConicalFrustum { + /// The radius of the top of the frustum + pub radius_top: f32, + /// The radius of the base of the frustum + pub radius_bottom: f32, + /// The height of the frustum + pub height: f32, +} +impl Primitive3d for ConicalFrustum {} + +/// The type of torus determined by the minor and major radii +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum TorusKind { + /// A torus that has a ring. + /// The major radius is greater than the minor radius + Ring, + /// A torus that has no hole but also doesn't intersect itself. + /// The major radius is equal to the minor radius + Horn, + /// A self-intersecting torus. + /// The major radius is less than the minor radius + Spindle, + /// A torus with non-geometric properties like + /// a minor or major radius that is non-positive, + /// infinite, or `NaN` + Invalid, +} + +/// A torus primitive, often representing a ring or donut shape +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Torus { + /// The radius of the tube of the torus + #[doc( + alias = "ring_radius", + alias = "tube_radius", + alias = "cross_section_radius" + )] + pub minor_radius: f32, + /// The distance from the center of the torus to the center of the tube + #[doc(alias = "radius_of_revolution")] + pub major_radius: f32, +} +impl Primitive3d for Torus {} + +impl Torus { + /// Create a new `Torus` from an inner and outer radius. + /// + /// The inner radius is the radius of the hole, and the outer radius + /// is the radius of the entire object + pub fn new(inner_radius: f32, outer_radius: f32) -> Self { + let minor_radius = (outer_radius - inner_radius) / 2.0; + let major_radius = outer_radius - minor_radius; + + Self { + minor_radius, + major_radius, + } + } + + /// Get the inner radius of the torus. + /// For a ring torus, this corresponds to the radius of the hole, + /// or `major_radius - minor_radius` + #[inline] + pub fn inner_radius(&self) -> f32 { + self.major_radius - self.minor_radius + } + + /// Get the outer radius of the torus. + /// This corresponds to the overall radius of the entire object, + /// or `major_radius + minor_radius` + #[inline] + pub fn outer_radius(&self) -> f32 { + self.major_radius + self.minor_radius + } + + /// Get the [`TorusKind`] determined by the minor and major radii. + /// + /// The torus can either be a *ring torus* that has a hole, + /// a *horn torus* that doesn't have a hole but also isn't self-intersecting, + /// or a *spindle torus* that is self-intersecting. + /// + /// If the minor or major radius is non-positive, infinite, or `NaN`, + /// [`TorusKind::Invalid`] is returned + #[inline] + pub fn kind(&self) -> TorusKind { + // Invalid if minor or major radius is non-positive, infinite, or NaN + if self.minor_radius <= 0.0 + || !self.minor_radius.is_finite() + || self.major_radius <= 0.0 + || !self.major_radius.is_finite() + { + return TorusKind::Invalid; + } + + match self.major_radius.partial_cmp(&self.minor_radius).unwrap() { + std::cmp::Ordering::Greater => TorusKind::Ring, + std::cmp::Ordering::Equal => TorusKind::Horn, + std::cmp::Ordering::Less => TorusKind::Spindle, + } + } +} diff --git a/crates/bevy_math/src/primitives/mod.rs b/crates/bevy_math/src/primitives/mod.rs new file mode 100644 index 0000000000000..da3532397b6ea --- /dev/null +++ b/crates/bevy_math/src/primitives/mod.rs @@ -0,0 +1,27 @@ +//! This module defines primitive shapes. +//! The origin is (0, 0) for 2D primitives and (0, 0, 0) for 3D primitives, +//! unless stated otherwise. + +mod dim2; +pub use dim2::*; +mod dim3; +pub use dim3::*; + +/// A marker trait for 2D primitives +pub trait Primitive2d {} + +/// A marker trait for 3D primitives +pub trait Primitive3d {} + +/// The winding order for a set of points +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum WindingOrder { + /// A clockwise winding order + Clockwise, + /// A counterclockwise winding order + CounterClockwise, + /// An invalid winding order indicating that it could not be computed reliably. + /// This often happens in *degenerate cases* where the points lie on the same line + #[doc(alias = "Degenerate")] + Invalid, +} diff --git a/crates/bevy_mikktspace/Cargo.toml b/crates/bevy_mikktspace/Cargo.toml index 105e730591f97..451e4e24a07a8 100644 --- a/crates/bevy_mikktspace/Cargo.toml +++ b/crates/bevy_mikktspace/Cargo.toml @@ -2,7 +2,11 @@ name = "bevy_mikktspace" version = "0.12.0" edition = "2021" -authors = ["Benjamin Wasty ", "David Harvey-Macaulay ", "Layl Bongers "] +authors = [ + "Benjamin Wasty ", + "David Harvey-Macaulay ", + "Layl Bongers ", +] description = "Mikkelsen tangent space algorithm" documentation = "https://docs.rs/bevy" homepage = "https://bevyengine.org" @@ -15,3 +19,6 @@ glam = "0.24.1" [[example]] name = "generate" + +[lints] +workspace = true diff --git a/crates/bevy_mikktspace/src/lib.rs b/crates/bevy_mikktspace/src/lib.rs index 365c2734800db..5d9e2e0d8ac07 100644 --- a/crates/bevy_mikktspace/src/lib.rs +++ b/crates/bevy_mikktspace/src/lib.rs @@ -1,5 +1,4 @@ -#![allow(clippy::type_complexity)] -#![allow(clippy::all)] +#![allow(clippy::all, clippy::undocumented_unsafe_blocks)] use glam::{Vec2, Vec3}; diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index 14759e5b7f55d..4980047775a0a 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -19,7 +19,9 @@ bevy_asset = { path = "../bevy_asset", version = "0.12.0" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" } bevy_math = { path = "../bevy_math", version = "0.12.0" } -bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] } +bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [ + "bevy", +] } bevy_render = { path = "../bevy_render", version = "0.12.0" } bevy_transform = { path = "../bevy_transform", version = "0.12.0" } bevy_utils = { path = "../bevy_utils", version = "0.12.0" } @@ -35,3 +37,6 @@ radsort = "0.1" naga_oil = "0.10" smallvec = "1.6" thread_local = "1.0" + +[lints] +workspace = true diff --git a/crates/bevy_pbr/src/extended_material.rs b/crates/bevy_pbr/src/extended_material.rs index 096c3f6315d1b..260051d05098a 100644 --- a/crates/bevy_pbr/src/extended_material.rs +++ b/crates/bevy_pbr/src/extended_material.rs @@ -1,5 +1,5 @@ use bevy_asset::{Asset, Handle}; -use bevy_reflect::TypePath; +use bevy_reflect::{impl_type_path, Reflect}; use bevy_render::{ mesh::MeshVertexBufferLayout, render_asset::RenderAssets, @@ -97,12 +97,17 @@ pub trait MaterialExtension: Asset + AsBindGroup + Clone + Sized { /// When used with `StandardMaterial` as the base, all the standard material fields are /// present, so the `pbr_fragment` shader functions can be called from the extension shader (see /// the `extended_material` example). -#[derive(Asset, Clone, TypePath)] +#[derive(Asset, Clone, Reflect)] +#[reflect(type_path = false)] pub struct ExtendedMaterial { pub base: B, pub extension: E, } +// We don't use the `TypePath` derive here due to a bug where `#[reflect(type_path = false)]` +// causes the `TypePath` derive to not generate an implementation. +impl_type_path!((in bevy_pbr::extended_material) ExtendedMaterial); + impl AsBindGroup for ExtendedMaterial { type Data = (::Data, ::Data); diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 8e1cfdd41d34b..d50c4028888a4 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -1,5 +1,3 @@ -#![allow(clippy::type_complexity)] - pub mod wireframe; mod alpha; diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 54ea52e5b972c..9902ce391c550 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -828,9 +828,13 @@ fn clip_to_view(inverse_projection: Mat4, clip: Vec4) -> Vec4 { pub fn add_clusters( mut commands: Commands, - cameras: Query<(Entity, Option<&ClusterConfig>), (With, Without)>, + cameras: Query<(Entity, Option<&ClusterConfig>, &Camera), Without>, ) { - for (entity, config) in &cameras { + for (entity, config, camera) in &cameras { + if !camera.is_active { + continue; + } + let config = config.copied().unwrap_or_default(); // actual settings here don't matter - they will be overwritten in assign_lights_to_clusters commands diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 62a7367520756..d7eff2ede461e 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -263,9 +263,13 @@ pub struct ExtractedClustersPointLights { pub fn extract_clusters( mut commands: Commands, - views: Extract>>, + views: Extract>, ) { - for (entity, clusters) in &views { + for (entity, clusters, camera) in &views { + if !camera.is_active { + continue; + } + commands.get_or_spawn(entity).insert(( ExtractedClustersPointLights { data: clusters.lights.clone(), diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 2d533e7e52f5c..dc1075090c891 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -36,6 +36,7 @@ impl Plugin for WireframePlugin { app.register_type::() .register_type::() .register_type::() + .register_type::() .init_resource::() .add_plugins(MaterialPlugin::::default()) .add_systems(Startup, setup_global_wireframe_material) diff --git a/crates/bevy_ptr/Cargo.toml b/crates/bevy_ptr/Cargo.toml index f09f5f0d5453d..06c5bd688c1d4 100644 --- a/crates/bevy_ptr/Cargo.toml +++ b/crates/bevy_ptr/Cargo.toml @@ -9,3 +9,6 @@ license = "MIT OR Apache-2.0" keywords = ["bevy", "no_std"] [dependencies] + +[lints] +workspace = true diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index f6212c505ba70..bebfc7de51a4c 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -1,7 +1,6 @@ #![doc = include_str!("../README.md")] #![no_std] #![warn(missing_docs)] -#![allow(clippy::type_complexity)] use core::fmt::{self, Formatter, Pointer}; use core::{ @@ -245,14 +244,14 @@ impl<'a, A: IsAligned> PtrMut<'a, A> { /// Gets a [`PtrMut`] from this with a smaller lifetime. #[inline] pub fn reborrow(&mut self) -> PtrMut<'_, A> { - // SAFE: the ptrmut we're borrowing from is assumed to be valid + // SAFETY: 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<'_, A> { - // SAFE: The `PtrMut` type's guarantees about the validity of this pointer are a superset of `Ptr` s guarantees + // SAFETY: The `PtrMut` type's guarantees about the validity of this pointer are a superset of `Ptr` s guarantees unsafe { Ptr::new(self.0) } } } @@ -328,14 +327,14 @@ impl<'a, A: IsAligned> OwningPtr<'a, A> { /// Gets an immutable pointer from this owned pointer. #[inline] pub fn as_ref(&self) -> Ptr<'_, A> { - // SAFE: The `Owning` type's guarantees about the validity of this pointer are a superset of `Ptr` s guarantees + // SAFETY: 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<'_, A> { - // SAFE: The `Owning` type's guarantees about the validity of this pointer are a superset of `Ptr` s guarantees + // SAFETY: The `Owning` type's guarantees about the validity of this pointer are a superset of `Ptr` s guarantees unsafe { PtrMut::new(self.0) } } } diff --git a/crates/bevy_reflect/Cargo.toml b/crates/bevy_reflect/Cargo.toml index ecf925cf019ae..e5261c34e2c7c 100644 --- a/crates/bevy_reflect/Cargo.toml +++ b/crates/bevy_reflect/Cargo.toml @@ -19,7 +19,7 @@ documentation = ["bevy_reflect_derive/documentation"] [dependencies] # bevy bevy_math = { path = "../bevy_math", version = "0.12.0", features = [ - "serialize", + "serialize", ], optional = true } bevy_reflect_derive = { path = "bevy_reflect_derive", version = "0.12.0" } bevy_utils = { path = "../bevy_utils", version = "0.12.0" } @@ -31,9 +31,9 @@ downcast-rs = "1.2" thiserror = "1.0" serde = "1" smallvec = { version = "1.6", features = [ - "serde", - "union", - "const_generics", + "serde", + "union", + "const_generics", ], optional = true } glam = { version = "0.24.1", features = ["serde"], optional = true } smol_str = { version = "0.2.0", optional = true } @@ -47,3 +47,6 @@ bincode = "1.3" name = "reflect_docs" path = "examples/reflect_docs.rs" required-features = ["documentation"] + +[lints] +workspace = true diff --git a/crates/bevy_reflect/bevy_reflect_derive/Cargo.toml b/crates/bevy_reflect/bevy_reflect_derive/Cargo.toml index 0950536e31796..6caf9948a374c 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/Cargo.toml +++ b/crates/bevy_reflect/bevy_reflect_derive/Cargo.toml @@ -23,3 +23,6 @@ syn = { version = "2.0", features = ["full"] } proc-macro2 = "1.0" quote = "1.0" uuid = { version = "1.1", features = ["v4"] } + +[lints] +workspace = true diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 1a02cf4ed838d..84aae3675fc4f 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -464,7 +464,6 @@ //! [orphan rule]: https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type:~:text=But%20we%20can%E2%80%99t,implementation%20to%20use. //! [`bevy_reflect_derive/documentation`]: bevy_reflect_derive //! [derive `Reflect`]: derive@crate::Reflect -#![allow(clippy::type_complexity)] mod array; mod fields; diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index be65412bcb773..6d9f78b7f3d8e 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -45,7 +45,9 @@ bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" } bevy_log = { path = "../bevy_log", version = "0.12.0" } bevy_math = { path = "../bevy_math", version = "0.12.0" } bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.12.0" } -bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] } +bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [ + "bevy", +] } bevy_render_macros = { path = "macros", version = "0.12.0" } bevy_time = { path = "../bevy_time", version = "0.12.0" } bevy_transform = { path = "../bevy_transform", version = "0.12.0" } @@ -60,7 +62,10 @@ image = { version = "0.24", default-features = false } codespan-reporting = "0.11.0" # `fragile-send-sync-non-atomic-wasm` feature means we can't use WASM threads for rendering # It is enabled for now to avoid having to do a significant overhaul of the renderer just for wasm -wgpu = { version = "0.17.1", features = ["naga", "fragile-send-sync-non-atomic-wasm"] } +wgpu = { version = "0.17.1", features = [ + "naga", + "fragile-send-sync-non-atomic-wasm", +] } naga = { version = "0.13.0", features = ["wgsl-in"] } naga_oil = "0.10" serde = { version = "1", features = ["derive"] } @@ -81,18 +86,23 @@ ruzstd = { version = "0.4.0", optional = true } basis-universal = { version = "0.3.0", optional = true } encase = { version = "0.6.1", features = ["glam"] } # For wgpu profiling using tracing. Use `RUST_LOG=info` to also capture the wgpu spans. -profiling = { version = "1", features = ["profile-with-tracing"], optional = true } +profiling = { version = "1", features = [ + "profile-with-tracing", +], optional = true } async-channel = "1.8" [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = "0.3" web-sys = { version = "0.3", features = [ - 'Blob', - 'Document', - 'Element', - 'HtmlElement', - 'Node', - 'Url', - 'Window', + 'Blob', + 'Document', + 'Element', + 'HtmlElement', + 'Node', + 'Url', + 'Window', ] } wasm-bindgen = "0.2" + +[lints] +workspace = true diff --git a/crates/bevy_render/macros/Cargo.toml b/crates/bevy_render/macros/Cargo.toml index 6bd810a310bb9..368077ef07b1d 100644 --- a/crates/bevy_render/macros/Cargo.toml +++ b/crates/bevy_render/macros/Cargo.toml @@ -17,3 +17,6 @@ bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.12.0" } syn = "2.0" proc-macro2 = "1.0" quote = "1.0" + +[lints] +workspace = true diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index be6349689b7cb..14f9ae72511fa 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -582,12 +582,9 @@ pub fn camera_system( let changed_image_handles: HashSet<&AssetId> = image_asset_events .read() - .filter_map(|event| { - if let AssetEvent::Modified { id } = event { - Some(id) - } else { - None - } + .filter_map(|event| match event { + AssetEvent::Modified { id } | AssetEvent::Added { id } => Some(id), + _ => None, }) .collect(); diff --git a/crates/bevy_render/src/color/mod.rs b/crates/bevy_render/src/color/mod.rs index b4139b8787482..dbe28a11acce6 100644 --- a/crates/bevy_render/src/color/mod.rs +++ b/crates/bevy_render/src/color/mod.rs @@ -5,7 +5,7 @@ pub use colorspace::*; use bevy_math::{Vec3, Vec4}; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use serde::{Deserialize, Serialize}; -use std::ops::{Add, AddAssign, Mul, MulAssign}; +use std::ops::{Add, Mul, MulAssign}; use thiserror::Error; #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)] @@ -1102,69 +1102,184 @@ impl Color { } } } -} -impl Default for Color { - fn default() -> Self { - Color::WHITE + /// New `Color` from `[f32; 4]` (or a type that can be converted into them) with RGB representation in sRGB colorspace. + #[inline] + pub fn rgba_from_array(arr: impl Into<[f32; 4]>) -> Self { + let [r, g, b, a]: [f32; 4] = arr.into(); + Color::rgba(r, g, b, a) } -} -impl AddAssign for Color { - fn add_assign(&mut self, rhs: Color) { - match self { + /// New `Color` from `[f32; 3]` (or a type that can be converted into them) with RGB representation in sRGB colorspace. + #[inline] + pub fn rgb_from_array(arr: impl Into<[f32; 3]>) -> Self { + let [r, g, b]: [f32; 3] = arr.into(); + Color::rgb(r, g, b) + } + + /// New `Color` from `[f32; 4]` (or a type that can be converted into them) with RGB representation in linear RGB colorspace. + #[inline] + pub fn rgba_linear_from_array(arr: impl Into<[f32; 4]>) -> Self { + let [r, g, b, a]: [f32; 4] = arr.into(); + Color::rgba_linear(r, g, b, a) + } + + /// New `Color` from `[f32; 3]` (or a type that can be converted into them) with RGB representation in linear RGB colorspace. + #[inline] + pub fn rgb_linear_from_array(arr: impl Into<[f32; 3]>) -> Self { + let [r, g, b]: [f32; 3] = arr.into(); + Color::rgb_linear(r, g, b) + } + + /// New `Color` from `[f32; 4]` (or a type that can be converted into them) with HSL representation in sRGB colorspace. + #[inline] + pub fn hsla_from_array(arr: impl Into<[f32; 4]>) -> Self { + let [h, s, l, a]: [f32; 4] = arr.into(); + Color::hsla(h, s, l, a) + } + + /// New `Color` from `[f32; 3]` (or a type that can be converted into them) with HSL representation in sRGB colorspace. + #[inline] + pub fn hsl_from_array(arr: impl Into<[f32; 3]>) -> Self { + let [h, s, l]: [f32; 3] = arr.into(); + Color::hsl(h, s, l) + } + + /// New `Color` from `[f32; 4]` (or a type that can be converted into them) with LCH representation in sRGB colorspace. + #[inline] + pub fn lcha_from_array(arr: impl Into<[f32; 4]>) -> Self { + let [l, c, h, a]: [f32; 4] = arr.into(); + Color::lcha(l, c, h, a) + } + + /// New `Color` from `[f32; 3]` (or a type that can be converted into them) with LCH representation in sRGB colorspace. + #[inline] + pub fn lch_from_array(arr: impl Into<[f32; 3]>) -> Self { + let [l, c, h]: [f32; 3] = arr.into(); + Color::lch(l, c, h) + } + + /// Convert `Color` to RGBA and return as `Vec4`. + #[inline] + pub fn rgba_to_vec4(&self) -> Vec4 { + let color = self.as_rgba(); + match color { Color::Rgba { red, green, blue, alpha, - } => { - let rhs = rhs.as_rgba_f32(); - *red += rhs[0]; - *green += rhs[1]; - *blue += rhs[2]; - *alpha += rhs[3]; - } + } => Vec4::new(red, green, blue, alpha), + _ => unreachable!(), + } + } + + /// Convert `Color` to RGBA and return as `Vec3`. + #[inline] + pub fn rgb_to_vec3(&self) -> Vec3 { + let color = self.as_rgba(); + match color { + Color::Rgba { + red, green, blue, .. + } => Vec3::new(red, green, blue), + _ => unreachable!(), + } + } + + /// Convert `Color` to linear RGBA and return as `Vec4`. + #[inline] + pub fn rgba_linear_to_vec4(&self) -> Vec4 { + let color = self.as_rgba_linear(); + match color { Color::RgbaLinear { red, green, blue, alpha, - } => { - let rhs = rhs.as_linear_rgba_f32(); - *red += rhs[0]; - *green += rhs[1]; - *blue += rhs[2]; - *alpha += rhs[3]; - } + } => Vec4::new(red, green, blue, alpha), + _ => unreachable!(), + } + } + + /// Convert `Color` to linear RGBA and return as `Vec3`. + #[inline] + pub fn rgb_linear_to_vec3(&self) -> Vec3 { + let color = self.as_rgba_linear(); + match color { + Color::RgbaLinear { + red, green, blue, .. + } => Vec3::new(red, green, blue), + _ => unreachable!(), + } + } + + /// Convert `Color` to HSLA and return as `Vec4`. + #[inline] + pub fn hsla_to_vec4(&self) -> Vec4 { + let color = self.as_hsla(); + match color { Color::Hsla { hue, saturation, lightness, alpha, - } => { - let rhs = rhs.as_hsla_f32(); - *hue += rhs[0]; - *saturation += rhs[1]; - *lightness += rhs[2]; - *alpha += rhs[3]; - } + } => Vec4::new(hue, saturation, lightness, alpha), + _ => unreachable!(), + } + } + + /// Convert `Color` to HSLA and return as `Vec3`. + #[inline] + pub fn hsl_to_vec3(&self) -> Vec3 { + let color = self.as_hsla(); + match color { + Color::Hsla { + hue, + saturation, + lightness, + .. + } => Vec3::new(hue, saturation, lightness), + _ => unreachable!(), + } + } + + /// Convert `Color` to LCHA and return as `Vec4`. + #[inline] + pub fn lcha_to_vec4(&self) -> Vec4 { + let color = self.as_lcha(); + match color { Color::Lcha { lightness, chroma, hue, alpha, - } => { - let rhs = rhs.as_lcha_f32(); - *lightness += rhs[0]; - *chroma += rhs[1]; - *hue += rhs[2]; - *alpha += rhs[3]; - } + } => Vec4::new(lightness, chroma, hue, alpha), + _ => unreachable!(), + } + } + + /// Convert `Color` to LCHA and return as `Vec3`. + #[inline] + pub fn lch_to_vec3(&self) -> Vec3 { + let color = self.as_lcha(); + match color { + Color::Lcha { + lightness, + chroma, + hue, + .. + } => Vec3::new(lightness, chroma, hue), + _ => unreachable!(), } } } +impl Default for Color { + fn default() -> Self { + Color::WHITE + } +} + impl Add for Color { type Output = Color; @@ -1219,7 +1334,6 @@ impl Add for Color { alpha, } => { let rhs = rhs.as_lcha_f32(); - Color::Lcha { lightness: lightness + rhs[0], chroma: chroma + rhs[1], @@ -1231,53 +1345,6 @@ impl Add for Color { } } -impl AddAssign for Color { - fn add_assign(&mut self, rhs: Vec4) { - let rhs: Color = rhs.into(); - *self += rhs; - } -} - -impl Add for Color { - type Output = Color; - - fn add(self, rhs: Vec4) -> Self::Output { - let rhs: Color = rhs.into(); - self + rhs - } -} - -impl From for [f32; 4] { - fn from(color: Color) -> Self { - color.as_rgba_f32() - } -} - -impl From<[f32; 4]> for Color { - fn from([r, g, b, a]: [f32; 4]) -> Self { - Color::rgba(r, g, b, a) - } -} - -impl From<[f32; 3]> for Color { - fn from([r, g, b]: [f32; 3]) -> Self { - Color::rgb(r, g, b) - } -} - -impl From for Vec4 { - fn from(color: Color) -> Self { - let color: [f32; 4] = color.into(); - Vec4::new(color[0], color[1], color[2], color[3]) - } -} - -impl From for Color { - fn from(vec4: Vec4) -> Self { - Color::rgba(vec4.x, vec4.y, vec4.z, vec4.w) - } -} - impl From for wgpu::Color { fn from(color: Color) -> Self { if let Color::RgbaLinear { @@ -1909,15 +1976,15 @@ mod tests { #[test] fn conversions_vec4() { let starting_vec4 = Vec4::new(0.4, 0.5, 0.6, 1.0); - let starting_color = Color::from(starting_vec4); + let starting_color = Color::rgba_from_array(starting_vec4); - assert_eq!(starting_vec4, Vec4::from(starting_color)); + assert_eq!(starting_vec4, starting_color.rgba_to_vec4()); let transformation = Vec4::new(0.5, 0.5, 0.5, 1.0); assert_eq!( starting_color * transformation, - Color::from(starting_vec4 * transformation), + Color::rgba_from_array(starting_vec4 * transformation) ); } diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 4f06c11e0291d..9e560f22ad855 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -1,5 +1,3 @@ -#![allow(clippy::type_complexity)] - #[cfg(target_pointer_width = "16")] compile_error!("bevy_render cannot compile for a 16-bit platform."); @@ -253,6 +251,7 @@ impl Plugin for RenderPlugin { app.insert_resource(FutureRendererResources( future_renderer_resources_wrapper.clone(), )); + // SAFETY: Plugins should be set up on the main thread. unsafe { initialize_render_app(app) }; } RenderCreation::Automatic(render_creation) => { @@ -273,8 +272,8 @@ impl Plugin for RenderPlugin { backends, dx12_shader_compiler: settings.dx12_shader_compiler.clone(), }); + // SAFETY: Plugins should be set up on the main thread. let surface = primary_window.map(|wrapper| unsafe { - // SAFETY: Plugins should be set up on the main thread. let handle = wrapper.get_handle(); instance .create_surface(&handle) @@ -315,6 +314,7 @@ impl Plugin for RenderPlugin { #[cfg(not(target_arch = "wasm32"))] futures_lite::future::block_on(async_renderer); + // SAFETY: Plugins should be set up on the main thread. unsafe { initialize_render_app(app) }; } } @@ -455,7 +455,7 @@ unsafe fn initialize_render_app(app: &mut App) { "An entity was spawned after the entity list was cleared last frame and before the extract schedule began. This is not supported", ); - // This is safe given the clear_entities call in the past frame and the assert above + // SAFETY: This is safe given the clear_entities call in the past frame and the assert above unsafe { render_app .world diff --git a/crates/bevy_render/src/render_resource/mod.rs b/crates/bevy_render/src/render_resource/mod.rs index b7d245b0bdbc9..3ed787f257d65 100644 --- a/crates/bevy_render/src/render_resource/mod.rs +++ b/crates/bevy_render/src/render_resource/mod.rs @@ -32,22 +32,23 @@ pub use uniform_buffer::*; pub use wgpu::{ util::BufferInitDescriptor, AdapterInfo as WgpuAdapterInfo, AddressMode, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, - BlendComponent, BlendFactor, BlendOperation, BlendState, BufferAddress, BufferBinding, - BufferBindingType, BufferDescriptor, BufferSize, BufferUsages, ColorTargetState, ColorWrites, - CommandEncoder, CommandEncoderDescriptor, CompareFunction, ComputePass, ComputePassDescriptor, - ComputePipelineDescriptor as RawComputePipelineDescriptor, DepthBiasState, DepthStencilState, - Extent3d, Face, Features as WgpuFeatures, FilterMode, FragmentState as RawFragmentState, - FrontFace, ImageCopyBuffer, ImageCopyBufferBase, ImageCopyTexture, ImageCopyTextureBase, - ImageDataLayout, ImageSubresourceRange, IndexFormat, Limits as WgpuLimits, LoadOp, MapMode, - MultisampleState, Operations, Origin3d, PipelineLayout, PipelineLayoutDescriptor, PolygonMode, - PrimitiveState, PrimitiveTopology, PushConstantRange, RenderPassColorAttachment, - RenderPassDepthStencilAttachment, RenderPassDescriptor, - RenderPipelineDescriptor as RawRenderPipelineDescriptor, SamplerBindingType, SamplerDescriptor, - ShaderModule, ShaderModuleDescriptor, ShaderSource, ShaderStages, StencilFaceState, - StencilOperation, StencilState, StorageTextureAccess, TextureAspect, TextureDescriptor, - TextureDimension, TextureFormat, TextureSampleType, TextureUsages, TextureViewDescriptor, - TextureViewDimension, VertexAttribute, VertexBufferLayout as RawVertexBufferLayout, - VertexFormat, VertexState as RawVertexState, VertexStepMode, + BlendComponent, BlendFactor, BlendOperation, BlendState, BufferAddress, BufferAsyncError, + BufferBinding, BufferBindingType, BufferDescriptor, BufferSize, BufferUsages, ColorTargetState, + ColorWrites, CommandEncoder, CommandEncoderDescriptor, CompareFunction, ComputePass, + ComputePassDescriptor, ComputePipelineDescriptor as RawComputePipelineDescriptor, + DepthBiasState, DepthStencilState, Extent3d, Face, Features as WgpuFeatures, FilterMode, + FragmentState as RawFragmentState, FrontFace, ImageCopyBuffer, ImageCopyBufferBase, + ImageCopyTexture, ImageCopyTextureBase, ImageDataLayout, ImageSubresourceRange, IndexFormat, + Limits as WgpuLimits, LoadOp, Maintain, MapMode, MultisampleState, Operations, Origin3d, + PipelineLayout, PipelineLayoutDescriptor, PolygonMode, PrimitiveState, PrimitiveTopology, + PushConstantRange, RenderPassColorAttachment, RenderPassDepthStencilAttachment, + RenderPassDescriptor, RenderPipelineDescriptor as RawRenderPipelineDescriptor, + SamplerBindingType, SamplerDescriptor, ShaderModule, ShaderModuleDescriptor, ShaderSource, + ShaderStages, StencilFaceState, StencilOperation, StencilState, StorageTextureAccess, + TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, + TextureUsages, TextureViewDescriptor, TextureViewDimension, VertexAttribute, + VertexBufferLayout as RawVertexBufferLayout, VertexFormat, VertexState as RawVertexState, + VertexStepMode, }; pub mod encase { diff --git a/crates/bevy_render/src/render_resource/resource_macros.rs b/crates/bevy_render/src/render_resource/resource_macros.rs index a827fcc7ba3fb..ab5ce2cbebe19 100644 --- a/crates/bevy_render/src/render_resource/resource_macros.rs +++ b/crates/bevy_render/src/render_resource/resource_macros.rs @@ -58,6 +58,7 @@ macro_rules! render_resource_wrapper { // 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 omits these manual Send + Sync impls unsafe impl Send for $wrapper_type {} + // SAFETY: As explained above, we ensure correctness by checking that $wgpu_type implements Send and Sync. unsafe impl Sync for $wrapper_type {} const _: () = { trait AssertSendSyncBound: Send + Sync {} diff --git a/crates/bevy_render/src/render_resource/shader.rs b/crates/bevy_render/src/render_resource/shader.rs index 465fbb193d35a..7b10677784c8f 100644 --- a/crates/bevy_render/src/render_resource/shader.rs +++ b/crates/bevy_render/src/render_resource/shader.rs @@ -259,30 +259,24 @@ impl AssetLoader for ShaderLoader { ) -> BoxedFuture<'a, Result> { Box::pin(async move { let ext = load_context.path().extension().unwrap().to_str().unwrap(); - + let path = load_context.asset_path().to_string(); + // On windows, the path will inconsistently use \ or /. + // TODO: remove this once AssetPath forces cross-platform "slash" consistency. See #10511 + let path = path.replace(std::path::MAIN_SEPARATOR, "/"); let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?; let mut shader = match ext { "spv" => Shader::from_spirv(bytes, load_context.path().to_string_lossy()), - "wgsl" => Shader::from_wgsl( - String::from_utf8(bytes)?, - load_context.path().to_string_lossy(), - ), - "vert" => Shader::from_glsl( - String::from_utf8(bytes)?, - naga::ShaderStage::Vertex, - load_context.path().to_string_lossy(), - ), - "frag" => Shader::from_glsl( - String::from_utf8(bytes)?, - naga::ShaderStage::Fragment, - load_context.path().to_string_lossy(), - ), - "comp" => Shader::from_glsl( - String::from_utf8(bytes)?, - naga::ShaderStage::Compute, - load_context.path().to_string_lossy(), - ), + "wgsl" => Shader::from_wgsl(String::from_utf8(bytes)?, path), + "vert" => { + Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Vertex, path) + } + "frag" => { + Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Fragment, path) + } + "comp" => { + Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Compute, path) + } _ => panic!("unhandled extension: {ext}"), }; diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 0bd224f4d4dd4..49bb64b689c0e 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -384,7 +384,12 @@ fn reset_view_visibility(mut query: Query<&mut ViewVisibility>) { /// for that view. pub fn check_visibility( mut thread_queues: Local>>>, - mut view_query: Query<(&mut VisibleEntities, &Frustum, Option<&RenderLayers>), With>, + mut view_query: Query<( + &mut VisibleEntities, + &Frustum, + Option<&RenderLayers>, + &Camera, + )>, mut visible_aabb_query: Query<( Entity, &InheritedVisibility, @@ -395,7 +400,11 @@ pub fn check_visibility( Has, )>, ) { - for (mut visible_entities, frustum, maybe_view_mask) in &mut view_query { + for (mut visible_entities, frustum, maybe_view_mask, camera) in &mut view_query { + if !camera.is_active { + continue; + } + let view_mask = maybe_view_mask.copied().unwrap_or_default(); visible_entities.entities.clear(); diff --git a/crates/bevy_render/src/view/window/mod.rs b/crates/bevy_render/src/view/window/mod.rs index 31ac4fca2cee3..b9e62c6a6b151 100644 --- a/crates/bevy_render/src/view/window/mod.rs +++ b/crates/bevy_render/src/view/window/mod.rs @@ -252,12 +252,15 @@ pub fn prepare_windows( let surface_data = window_surfaces .surfaces .entry(window.entity) - .or_insert_with(|| unsafe { - // NOTE: On some OSes this MUST be called from the main thread. - // As of wgpu 0.15, only fallible if the given window is a HTML canvas and obtaining a WebGPU or WebGL2 context fails. - let surface = render_instance - .create_surface(&window.handle.get_handle()) - .expect("Failed to create wgpu surface"); + .or_insert_with(|| { + // SAFETY: The window handles in ExtractedWindows will always be valid objects to create surfaces on + let surface = unsafe { + // NOTE: On some OSes this MUST be called from the main thread. + // As of wgpu 0.15, only fallible if the given window is a HTML canvas and obtaining a WebGPU or WebGL2 context fails. + render_instance + .create_surface(&window.handle.get_handle()) + .expect("Failed to create wgpu surface") + }; let caps = surface.get_capabilities(&render_adapter); let formats = caps.formats; // For future HDR output support, we'll need to request a format that supports HDR, diff --git a/crates/bevy_scene/Cargo.toml b/crates/bevy_scene/Cargo.toml index 4f8a5eadc060d..a08f4d483fd5e 100644 --- a/crates/bevy_scene/Cargo.toml +++ b/crates/bevy_scene/Cargo.toml @@ -18,7 +18,9 @@ bevy_app = { path = "../bevy_app", version = "0.12.0" } bevy_asset = { path = "../bevy_asset", version = "0.12.0" } bevy_derive = { path = "../bevy_derive", version = "0.12.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" } -bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] } +bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [ + "bevy", +] } bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" } bevy_transform = { path = "../bevy_transform", version = "0.12.0" } bevy_utils = { path = "../bevy_utils", version = "0.12.0" } @@ -26,7 +28,6 @@ bevy_render = { path = "../bevy_render", version = "0.12.0", optional = true } # other serde = { version = "1.0", features = ["derive"], optional = true } -ron = "0.8.0" uuid = { version = "1.1", features = ["v4"] } thiserror = "1.0" @@ -34,3 +35,6 @@ thiserror = "1.0" postcard = { version = "1.0", features = ["alloc"] } bincode = "1.3" rmp-serde = "1.1" + +[lints] +workspace = true diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index fa7100652fd1c..7b57caafa98cd 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -1,4 +1,4 @@ -use crate::{DynamicSceneBuilder, Scene, SceneSpawnError}; +use crate::{ron, DynamicSceneBuilder, Scene, SceneSpawnError}; use bevy_ecs::{ entity::Entity, reflect::{AppTypeRegistry, ReflectComponent, ReflectMapEntities}, diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 4ca3c303c30ed..cb4988c8be8b1 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -4,7 +4,6 @@ //! instantiated or removed from a world to allow composition. Scenes can be serialized/deserialized, //! for example to save part of the world state to a file. -#![allow(clippy::type_complexity)] #![warn(missing_docs)] mod bundle; @@ -18,6 +17,9 @@ mod scene_spawner; #[cfg(feature = "serialize")] pub mod serde; +/// Rusty Object Notation, a crate used to serialize and deserialize bevy scenes. +pub use bevy_asset::ron; + use bevy_ecs::schedule::IntoSystemConfigs; pub use bundle::*; pub use dynamic_scene::*; diff --git a/crates/bevy_scene/src/scene_loader.rs b/crates/bevy_scene/src/scene_loader.rs index 5d1e4de56ade4..f4dce7c66a3d8 100644 --- a/crates/bevy_scene/src/scene_loader.rs +++ b/crates/bevy_scene/src/scene_loader.rs @@ -1,3 +1,4 @@ +use crate::ron; #[cfg(feature = "serialize")] use crate::serde::SceneDeserializer; use crate::DynamicScene; diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 11192337b6ac9..f8908736dc794 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -1,5 +1,5 @@ use crate::{DynamicScene, Scene}; -use bevy_asset::{AssetEvent, AssetId, Assets}; +use bevy_asset::{AssetEvent, AssetId, Assets, Handle}; use bevy_ecs::{ entity::Entity, event::{Event, Events, ManualEventReader}, @@ -63,8 +63,8 @@ pub struct SceneSpawner { spawned_dynamic_scenes: HashMap, Vec>, spawned_instances: HashMap, scene_asset_event_reader: ManualEventReader>, - dynamic_scenes_to_spawn: Vec<(AssetId, InstanceId)>, - scenes_to_spawn: Vec<(AssetId, InstanceId)>, + dynamic_scenes_to_spawn: Vec<(Handle, InstanceId)>, + scenes_to_spawn: Vec<(Handle, InstanceId)>, scenes_to_despawn: Vec>, instances_to_despawn: Vec, scenes_with_parent: Vec<(InstanceId, Entity)>, @@ -127,7 +127,7 @@ pub enum SceneSpawnError { impl SceneSpawner { /// Schedule the spawn of a new instance of the provided dynamic scene. - pub fn spawn_dynamic(&mut self, id: impl Into>) -> InstanceId { + pub fn spawn_dynamic(&mut self, id: impl Into>) -> InstanceId { let instance_id = InstanceId::new(); self.dynamic_scenes_to_spawn.push((id.into(), instance_id)); instance_id @@ -136,7 +136,7 @@ impl SceneSpawner { /// Schedule the spawn of a new instance of the provided dynamic scene as a child of `parent`. pub fn spawn_dynamic_as_child( &mut self, - id: impl Into>, + id: impl Into>, parent: Entity, ) -> InstanceId { let instance_id = InstanceId::new(); @@ -146,14 +146,14 @@ impl SceneSpawner { } /// Schedule the spawn of a new instance of the provided scene. - pub fn spawn(&mut self, id: impl Into>) -> InstanceId { + pub fn spawn(&mut self, id: impl Into>) -> InstanceId { let instance_id = InstanceId::new(); self.scenes_to_spawn.push((id.into(), instance_id)); instance_id } /// Schedule the spawn of a new instance of the provided scene as a child of `parent`. - pub fn spawn_as_child(&mut self, id: impl Into>, parent: Entity) -> InstanceId { + pub fn spawn_as_child(&mut self, id: impl Into>, parent: Entity) -> InstanceId { let instance_id = InstanceId::new(); self.scenes_to_spawn.push((id.into(), instance_id)); self.scenes_with_parent.push((instance_id, parent)); @@ -296,21 +296,21 @@ impl SceneSpawner { pub fn spawn_queued_scenes(&mut self, world: &mut World) -> Result<(), SceneSpawnError> { let scenes_to_spawn = std::mem::take(&mut self.dynamic_scenes_to_spawn); - for (id, instance_id) in scenes_to_spawn { + for (handle, instance_id) in scenes_to_spawn { let mut entity_map = EntityHashMap::default(); - match Self::spawn_dynamic_internal(world, id, &mut entity_map) { + match Self::spawn_dynamic_internal(world, handle.id(), &mut entity_map) { Ok(_) => { self.spawned_instances .insert(instance_id, InstanceInfo { entity_map }); let spawned = self .spawned_dynamic_scenes - .entry(id) + .entry(handle.id()) .or_insert_with(Vec::new); spawned.push(instance_id); } Err(SceneSpawnError::NonExistentScene { .. }) => { - self.dynamic_scenes_to_spawn.push((id, instance_id)); + self.dynamic_scenes_to_spawn.push((handle, instance_id)); } Err(err) => return Err(err), } @@ -319,10 +319,10 @@ impl SceneSpawner { let scenes_to_spawn = std::mem::take(&mut self.scenes_to_spawn); for (scene_handle, instance_id) in scenes_to_spawn { - match self.spawn_sync_internal(world, scene_handle, instance_id) { + match self.spawn_sync_internal(world, scene_handle.id(), instance_id) { Ok(_) => {} - Err(SceneSpawnError::NonExistentRealScene { id: handle }) => { - self.scenes_to_spawn.push((handle, instance_id)); + Err(SceneSpawnError::NonExistentRealScene { .. }) => { + self.scenes_to_spawn.push((scene_handle, instance_id)); } Err(err) => return Err(err), } diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index 7a8cfe090f7c2..8180e227a8ce8 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -496,6 +496,7 @@ impl<'a, 'de> Visitor<'de> for SceneMapVisitor<'a> { #[cfg(test)] mod tests { + use crate::ron; use crate::serde::{SceneDeserializer, SceneSerializer}; use crate::{DynamicScene, DynamicSceneBuilder}; use bevy_ecs::entity::{Entity, EntityMapper, MapEntities}; diff --git a/crates/bevy_sprite/Cargo.toml b/crates/bevy_sprite/Cargo.toml index 9b211f9d41ebf..b09603430abce 100644 --- a/crates/bevy_sprite/Cargo.toml +++ b/crates/bevy_sprite/Cargo.toml @@ -20,7 +20,7 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" } bevy_log = { path = "../bevy_log", version = "0.12.0" } bevy_math = { path = "../bevy_math", version = "0.12.0" } bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [ - "bevy", + "bevy", ] } bevy_render = { path = "../bevy_render", version = "0.12.0" } bevy_transform = { path = "../bevy_transform", version = "0.12.0" } @@ -35,3 +35,6 @@ thiserror = "1.0" rectangle-pack = "0.4" bitflags = "2.3" radsort = "0.1" + +[lints] +workspace = true diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 9f68a9a3b981e..ce5b12e6a5c89 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -1,5 +1,3 @@ -#![allow(clippy::type_complexity)] - mod bundle; mod dynamic_texture_atlas_builder; mod mesh2d; diff --git a/crates/bevy_tasks/Cargo.toml b/crates/bevy_tasks/Cargo.toml index 5974ce7508f7c..b1274ff47b601 100644 --- a/crates/bevy_tasks/Cargo.toml +++ b/crates/bevy_tasks/Cargo.toml @@ -24,3 +24,6 @@ wasm-bindgen-futures = "0.4" [dev-dependencies] web-time = { version = "0.2" } + +[lints] +workspace = true diff --git a/crates/bevy_tasks/src/lib.rs b/crates/bevy_tasks/src/lib.rs index d4b68e2096bc6..7b998c0db3909 100644 --- a/crates/bevy_tasks/src/lib.rs +++ b/crates/bevy_tasks/src/lib.rs @@ -1,5 +1,4 @@ #![warn(missing_docs)] -#![allow(clippy::type_complexity)] #![doc = include_str!("../README.md")] mod slice; @@ -16,7 +15,7 @@ pub use task_pool::{Scope, TaskPool, TaskPoolBuilder}; #[cfg(any(target_arch = "wasm32", not(feature = "multi-threaded")))] mod single_threaded_task_pool; #[cfg(any(target_arch = "wasm32", not(feature = "multi-threaded")))] -pub use single_threaded_task_pool::{Scope, TaskPool, TaskPoolBuilder, ThreadExecutor}; +pub use single_threaded_task_pool::{FakeTask, Scope, TaskPool, TaskPoolBuilder, ThreadExecutor}; mod usages; #[cfg(not(target_arch = "wasm32"))] @@ -36,6 +35,8 @@ pub use futures_lite::future::block_on; mod iter; pub use iter::ParallelIterator; +pub use futures_lite; + #[allow(missing_docs)] pub mod prelude { #[doc(hidden)] diff --git a/crates/bevy_tasks/src/single_threaded_task_pool.rs b/crates/bevy_tasks/src/single_threaded_task_pool.rs index 9555a6a470f7c..81c26a1bc4c1b 100644 --- a/crates/bevy_tasks/src/single_threaded_task_pool.rs +++ b/crates/bevy_tasks/src/single_threaded_task_pool.rs @@ -186,6 +186,9 @@ impl TaskPool { } } +/// An empty task used in single-threaded contexts. +/// +/// This does nothing and is therefore safe, and recommended, to ignore. #[derive(Debug)] pub struct FakeTask; diff --git a/crates/bevy_tasks/src/task_pool.rs b/crates/bevy_tasks/src/task_pool.rs index b538705b6742b..4c37ef3b7a190 100644 --- a/crates/bevy_tasks/src/task_pool.rs +++ b/crates/bevy_tasks/src/task_pool.rs @@ -353,12 +353,16 @@ impl TaskPool { // Any usages of the references passed into `Scope` must be accessed through // the transmuted reference for the rest of this function. let executor: &async_executor::Executor = &self.executor; + // SAFETY: As above, all futures must complete in this function so we can change the lifetime let executor: &'env async_executor::Executor = unsafe { mem::transmute(executor) }; + // SAFETY: As above, all futures must complete in this function so we can change the lifetime let external_executor: &'env ThreadExecutor<'env> = unsafe { mem::transmute(external_executor) }; + // SAFETY: As above, all futures must complete in this function so we can change the lifetime let scope_executor: &'env ThreadExecutor<'env> = unsafe { mem::transmute(scope_executor) }; let spawned: ConcurrentQueue> = ConcurrentQueue::unbounded(); // shadow the variable so that the owned value cannot be used for the rest of the function + // SAFETY: As above, all futures must complete in this function so we can change the lifetime let spawned: &'env ConcurrentQueue< FallibleTask>>, > = unsafe { mem::transmute(&spawned) }; @@ -373,6 +377,7 @@ impl TaskPool { }; // shadow the variable so that the owned value cannot be used for the rest of the function + // SAFETY: As above, all futures must complete in this function so we can change the lifetime let scope: &'env Scope<'_, 'env, T> = unsafe { mem::transmute(&scope) }; f(scope); diff --git a/crates/bevy_text/Cargo.toml b/crates/bevy_text/Cargo.toml index 1237443d78634..4c9e51609cb5e 100644 --- a/crates/bevy_text/Cargo.toml +++ b/crates/bevy_text/Cargo.toml @@ -18,7 +18,9 @@ bevy_app = { path = "../bevy_app", version = "0.12.0" } bevy_asset = { path = "../bevy_asset", version = "0.12.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" } bevy_math = { path = "../bevy_math", version = "0.12.0" } -bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] } +bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [ + "bevy", +] } bevy_render = { path = "../bevy_render", version = "0.12.0" } bevy_sprite = { path = "../bevy_sprite", version = "0.12.0" } bevy_transform = { path = "../bevy_transform", version = "0.12.0" } @@ -29,4 +31,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.12.0" } ab_glyph = "0.2.6" glyph_brush_layout = "0.2.1" thiserror = "1.0" -serde = {version = "1", features = ["derive"]} +serde = { version = "1", features = ["derive"] } + +[lints] +workspace = true diff --git a/crates/bevy_text/src/font.rs b/crates/bevy_text/src/font.rs index e968e37febb85..1b91430932e3b 100644 --- a/crates/bevy_text/src/font.rs +++ b/crates/bevy_text/src/font.rs @@ -20,11 +20,15 @@ impl Font { pub fn get_outlined_glyph_texture(outlined_glyph: OutlinedGlyph) -> Image { let bounds = outlined_glyph.px_bounds(); - let width = bounds.width() as usize; - let height = bounds.height() as usize; + // Increase the length of the glyph texture by 2-pixels on each axis to make space + // for a pixel wide transparent border along its edges. + let width = bounds.width() as usize + 2; + let height = bounds.height() as usize + 2; let mut alpha = vec![0.0; width * height]; outlined_glyph.draw(|x, y, v| { - alpha[y as usize * width + x as usize] = v; + // Displace the glyph by 1 pixel on each axis so that it is drawn in the center of the texture. + // This leaves a pixel wide transparent border around the glyph. + alpha[(y + 1) as usize * width + x as usize + 1] = v; }); // TODO: make this texture grayscale diff --git a/crates/bevy_text/src/font_atlas.rs b/crates/bevy_text/src/font_atlas.rs index be1903c121b36..cdf9e095889ba 100644 --- a/crates/bevy_text/src/font_atlas.rs +++ b/crates/bevy_text/src/font_atlas.rs @@ -65,7 +65,7 @@ impl FontAtlas { Self { texture_atlas: texture_atlases.add(texture_atlas), glyph_to_atlas_index: HashMap::default(), - dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder::new(size, 1), + dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder::new(size, 0), } } diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 6a1981fa6110b..bdbc1e168377e 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -1,5 +1,3 @@ -#![allow(clippy::type_complexity)] - mod error; mod font; mod font_atlas; diff --git a/crates/bevy_time/Cargo.toml b/crates/bevy_time/Cargo.toml index b8a47ac9699a2..f4399a779b323 100644 --- a/crates/bevy_time/Cargo.toml +++ b/crates/bevy_time/Cargo.toml @@ -16,11 +16,18 @@ bevy_ci_testing = ["bevy_app/bevy_ci_testing"] [dependencies] # bevy bevy_app = { path = "../bevy_app", version = "0.12.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.12.0", features = ["bevy_reflect"] } -bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] } +bevy_ecs = { path = "../bevy_ecs", version = "0.12.0", features = [ + "bevy_reflect", +] } +bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [ + "bevy", +] } bevy_utils = { path = "../bevy_utils", version = "0.12.0" } # other crossbeam-channel = "0.5.0" serde = { version = "1", features = ["derive"], optional = true } thiserror = "1.0" + +[lints] +workspace = true diff --git a/crates/bevy_time/src/fixed.rs b/crates/bevy_time/src/fixed.rs index 60270172aaa22..faef1d7d38839 100644 --- a/crates/bevy_time/src/fixed.rs +++ b/crates/bevy_time/src/fixed.rs @@ -175,17 +175,26 @@ impl Time { self.context().overstep } + /// Discard a part of the overstep amount. + /// + /// If `discard` is higher than overstep, the overstep becomes zero. + #[inline] + pub fn discard_overstep(&mut self, discard: Duration) { + let context = self.context_mut(); + context.overstep = context.overstep.saturating_sub(discard); + } + /// Returns the amount of overstep time accumulated toward new steps, as an /// [`f32`] fraction of the timestep. #[inline] - pub fn overstep_percentage(&self) -> f32 { + pub fn overstep_fraction(&self) -> f32 { self.context().overstep.as_secs_f32() / self.context().timestep.as_secs_f32() } /// Returns the amount of overstep time accumulated toward new steps, as an /// [`f64`] fraction of the timestep. #[inline] - pub fn overstep_percentage_f64(&self) -> f64 { + pub fn overstep_fraction_f64(&self) -> f64 { self.context().overstep.as_secs_f64() / self.context().timestep.as_secs_f64() } @@ -265,56 +274,56 @@ mod test { assert_eq!(time.delta(), Duration::ZERO); assert_eq!(time.elapsed(), Duration::ZERO); assert_eq!(time.overstep(), Duration::from_secs(1)); - assert_eq!(time.overstep_percentage(), 0.5); - assert_eq!(time.overstep_percentage_f64(), 0.5); + assert_eq!(time.overstep_fraction(), 0.5); + assert_eq!(time.overstep_fraction_f64(), 0.5); assert!(!time.expend()); // false assert_eq!(time.delta(), Duration::ZERO); assert_eq!(time.elapsed(), Duration::ZERO); assert_eq!(time.overstep(), Duration::from_secs(1)); - assert_eq!(time.overstep_percentage(), 0.5); - assert_eq!(time.overstep_percentage_f64(), 0.5); + assert_eq!(time.overstep_fraction(), 0.5); + assert_eq!(time.overstep_fraction_f64(), 0.5); time.accumulate(Duration::from_secs(1)); assert_eq!(time.delta(), Duration::ZERO); assert_eq!(time.elapsed(), Duration::ZERO); assert_eq!(time.overstep(), Duration::from_secs(2)); - assert_eq!(time.overstep_percentage(), 1.0); - assert_eq!(time.overstep_percentage_f64(), 1.0); + assert_eq!(time.overstep_fraction(), 1.0); + assert_eq!(time.overstep_fraction_f64(), 1.0); assert!(time.expend()); // true assert_eq!(time.delta(), Duration::from_secs(2)); assert_eq!(time.elapsed(), Duration::from_secs(2)); assert_eq!(time.overstep(), Duration::ZERO); - assert_eq!(time.overstep_percentage(), 0.0); - assert_eq!(time.overstep_percentage_f64(), 0.0); + assert_eq!(time.overstep_fraction(), 0.0); + assert_eq!(time.overstep_fraction_f64(), 0.0); assert!(!time.expend()); // false assert_eq!(time.delta(), Duration::from_secs(2)); assert_eq!(time.elapsed(), Duration::from_secs(2)); assert_eq!(time.overstep(), Duration::ZERO); - assert_eq!(time.overstep_percentage(), 0.0); - assert_eq!(time.overstep_percentage_f64(), 0.0); + assert_eq!(time.overstep_fraction(), 0.0); + assert_eq!(time.overstep_fraction_f64(), 0.0); time.accumulate(Duration::from_secs(1)); assert_eq!(time.delta(), Duration::from_secs(2)); assert_eq!(time.elapsed(), Duration::from_secs(2)); assert_eq!(time.overstep(), Duration::from_secs(1)); - assert_eq!(time.overstep_percentage(), 0.5); - assert_eq!(time.overstep_percentage_f64(), 0.5); + assert_eq!(time.overstep_fraction(), 0.5); + assert_eq!(time.overstep_fraction_f64(), 0.5); assert!(!time.expend()); // false assert_eq!(time.delta(), Duration::from_secs(2)); assert_eq!(time.elapsed(), Duration::from_secs(2)); assert_eq!(time.overstep(), Duration::from_secs(1)); - assert_eq!(time.overstep_percentage(), 0.5); - assert_eq!(time.overstep_percentage_f64(), 0.5); + assert_eq!(time.overstep_fraction(), 0.5); + assert_eq!(time.overstep_fraction_f64(), 0.5); } #[test] diff --git a/crates/bevy_time/src/lib.rs b/crates/bevy_time/src/lib.rs index d5611ec58ec36..2d47acf06b60c 100644 --- a/crates/bevy_time/src/lib.rs +++ b/crates/bevy_time/src/lib.rs @@ -1,4 +1,3 @@ -#![allow(clippy::type_complexity)] #![warn(missing_docs)] #![doc = include_str!("../README.md")] diff --git a/crates/bevy_time/src/timer.rs b/crates/bevy_time/src/timer.rs index e7152978ee561..8383843571919 100644 --- a/crates/bevy_time/src/timer.rs +++ b/crates/bevy_time/src/timer.rs @@ -334,7 +334,7 @@ impl Timer { self.times_finished_this_tick = 0; } - /// Returns the percentage of the timer elapsed time (goes from 0.0 to 1.0). + /// Returns the fraction of the timer elapsed time (goes from 0.0 to 1.0). /// /// # Examples /// ``` @@ -342,10 +342,10 @@ impl Timer { /// use std::time::Duration; /// let mut timer = Timer::from_seconds(2.0, TimerMode::Once); /// timer.tick(Duration::from_secs_f32(0.5)); - /// assert_eq!(timer.percent(), 0.25); + /// assert_eq!(timer.fraction(), 0.25); /// ``` #[inline] - pub fn percent(&self) -> f32 { + pub fn fraction(&self) -> f32 { if self.duration == Duration::ZERO { 1.0 } else { @@ -353,7 +353,7 @@ impl Timer { } } - /// Returns the percentage of the timer remaining time (goes from 1.0 to 0.0). + /// Returns the fraction of the timer remaining time (goes from 1.0 to 0.0). /// /// # Examples /// ``` @@ -361,11 +361,11 @@ impl Timer { /// use std::time::Duration; /// let mut timer = Timer::from_seconds(2.0, TimerMode::Once); /// timer.tick(Duration::from_secs_f32(0.5)); - /// assert_eq!(timer.percent_left(), 0.75); + /// assert_eq!(timer.fraction_remaining(), 0.75); /// ``` #[inline] - pub fn percent_left(&self) -> f32 { - 1.0 - self.percent() + pub fn fraction_remaining(&self) -> f32 { + 1.0 - self.fraction() } /// Returns the remaining time in seconds @@ -452,8 +452,8 @@ mod tests { assert!(!t.just_finished()); assert_eq!(t.times_finished_this_tick(), 0); assert_eq!(t.mode(), TimerMode::Once); - assert_eq!(t.percent(), 0.025); - assert_eq!(t.percent_left(), 0.975); + assert_eq!(t.fraction(), 0.025); + assert_eq!(t.fraction_remaining(), 0.975); // Ticking while paused changes nothing t.pause(); t.tick(Duration::from_secs_f32(500.0)); @@ -463,8 +463,8 @@ mod tests { assert!(!t.just_finished()); assert_eq!(t.times_finished_this_tick(), 0); assert_eq!(t.mode(), TimerMode::Once); - assert_eq!(t.percent(), 0.025); - assert_eq!(t.percent_left(), 0.975); + assert_eq!(t.fraction(), 0.025); + assert_eq!(t.fraction_remaining(), 0.975); // Tick past the end and make sure elapsed doesn't go past 0.0 and other things update t.unpause(); t.tick(Duration::from_secs_f32(500.0)); @@ -472,16 +472,16 @@ mod tests { assert!(t.finished()); assert!(t.just_finished()); assert_eq!(t.times_finished_this_tick(), 1); - assert_eq!(t.percent(), 1.0); - assert_eq!(t.percent_left(), 0.0); + assert_eq!(t.fraction(), 1.0); + assert_eq!(t.fraction_remaining(), 0.0); // Continuing to tick when finished should only change just_finished t.tick(Duration::from_secs_f32(1.0)); assert_eq!(t.elapsed_secs(), 10.0); assert!(t.finished()); assert!(!t.just_finished()); assert_eq!(t.times_finished_this_tick(), 0); - assert_eq!(t.percent(), 1.0); - assert_eq!(t.percent_left(), 0.0); + assert_eq!(t.fraction(), 1.0); + assert_eq!(t.fraction_remaining(), 0.0); } #[test] @@ -495,24 +495,24 @@ mod tests { assert!(!t.just_finished()); assert_eq!(t.times_finished_this_tick(), 0); assert_eq!(t.mode(), TimerMode::Repeating); - assert_eq!(t.percent(), 0.375); - assert_eq!(t.percent_left(), 0.625); + assert_eq!(t.fraction(), 0.375); + assert_eq!(t.fraction_remaining(), 0.625); // Tick past the end and make sure elapsed wraps t.tick(Duration::from_secs_f32(1.5)); assert_eq!(t.elapsed_secs(), 0.25); assert!(t.finished()); assert!(t.just_finished()); assert_eq!(t.times_finished_this_tick(), 1); - assert_eq!(t.percent(), 0.125); - assert_eq!(t.percent_left(), 0.875); + assert_eq!(t.fraction(), 0.125); + assert_eq!(t.fraction_remaining(), 0.875); // Continuing to tick should turn off both finished & just_finished for repeating timers t.tick(Duration::from_secs_f32(1.0)); assert_eq!(t.elapsed_secs(), 1.25); assert!(!t.finished()); assert!(!t.just_finished()); assert_eq!(t.times_finished_this_tick(), 0); - assert_eq!(t.percent(), 0.625); - assert_eq!(t.percent_left(), 0.375); + assert_eq!(t.fraction(), 0.625); + assert_eq!(t.fraction_remaining(), 0.375); } #[test] @@ -543,19 +543,19 @@ mod tests { let mut t = Timer::from_seconds(0.0, TimerMode::Repeating); assert_eq!(t.times_finished_this_tick(), 0); assert_eq!(t.elapsed(), Duration::ZERO); - assert_eq!(t.percent(), 1.0); + assert_eq!(t.fraction(), 1.0); t.tick(Duration::from_secs(1)); assert_eq!(t.times_finished_this_tick(), u32::MAX); assert_eq!(t.elapsed(), Duration::ZERO); - assert_eq!(t.percent(), 1.0); + assert_eq!(t.fraction(), 1.0); t.tick(Duration::from_secs(2)); assert_eq!(t.times_finished_this_tick(), u32::MAX); assert_eq!(t.elapsed(), Duration::ZERO); - assert_eq!(t.percent(), 1.0); + assert_eq!(t.fraction(), 1.0); t.reset(); assert_eq!(t.times_finished_this_tick(), 0); assert_eq!(t.elapsed(), Duration::ZERO); - assert_eq!(t.percent(), 1.0); + assert_eq!(t.fraction(), 1.0); } #[test] diff --git a/crates/bevy_transform/Cargo.toml b/crates/bevy_transform/Cargo.toml index 5ad19c096e8c6..4bd7ea5724a54 100644 --- a/crates/bevy_transform/Cargo.toml +++ b/crates/bevy_transform/Cargo.toml @@ -11,10 +11,14 @@ keywords = ["bevy"] [dependencies] # bevy bevy_app = { path = "../bevy_app", version = "0.12.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.12.0", features = ["bevy_reflect"] } +bevy_ecs = { path = "../bevy_ecs", version = "0.12.0", features = [ + "bevy_reflect", +] } bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" } bevy_math = { path = "../bevy_math", version = "0.12.0" } -bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] } +bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [ + "bevy", +] } serde = { version = "1", features = ["derive"], optional = true } thiserror = "1.0" @@ -25,3 +29,6 @@ glam = { version = "0.24", features = ["approx"] } [features] serialize = ["dep:serde", "bevy_math/serialize"] + +[lints] +workspace = true diff --git a/crates/bevy_transform/src/components/transform.rs b/crates/bevy_transform/src/components/transform.rs index 451579f6f304d..95a5881858e1c 100644 --- a/crates/bevy_transform/src/components/transform.rs +++ b/crates/bevy_transform/src/components/transform.rs @@ -396,6 +396,15 @@ impl Transform { point += self.translation; point } + + /// Returns `true` if, and only if, translation, rotation and scale all are + /// finite. If any of them contains a `NaN`, positive or negative infinity, + /// this will return `false`. + #[inline] + #[must_use] + pub fn is_finite(&self) -> bool { + self.translation.is_finite() && self.rotation.is_finite() && self.scale.is_finite() + } } impl Default for Transform { diff --git a/crates/bevy_transform/src/lib.rs b/crates/bevy_transform/src/lib.rs index 5c176c4b8bbc2..c7d16ea989a13 100644 --- a/crates/bevy_transform/src/lib.rs +++ b/crates/bevy_transform/src/lib.rs @@ -1,6 +1,4 @@ -#![allow(clippy::type_complexity)] #![warn(missing_docs)] -#![warn(clippy::undocumented_unsafe_blocks)] #![doc = include_str!("../README.md")] pub mod commands; diff --git a/crates/bevy_ui/Cargo.toml b/crates/bevy_ui/Cargo.toml index b6dd346f955ec..969fd3efff5fb 100644 --- a/crates/bevy_ui/Cargo.toml +++ b/crates/bevy_ui/Cargo.toml @@ -21,7 +21,7 @@ bevy_input = { path = "../bevy_input", version = "0.12.0" } bevy_log = { path = "../bevy_log", version = "0.12.0" } bevy_math = { path = "../bevy_math", version = "0.12.0" } bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [ - "bevy", + "bevy", ] } bevy_render = { path = "../bevy_render", version = "0.12.0" } bevy_sprite = { path = "../bevy_sprite", version = "0.12.0" } @@ -36,3 +36,6 @@ serde = { version = "1", features = ["derive"] } smallvec = { version = "1.6", features = ["union", "const_generics"] } bytemuck = { version = "1.5", features = ["derive"] } thiserror = "1.0.0" + +[lints] +workspace = true diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index e72eb5ea777d2..f8b2597cc2088 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -1,5 +1,3 @@ -#![allow(clippy::type_complexity)] - //! This crate contains Bevy's UI system, which can be used to create UI for both 2D and 3D games //! # Basic usage //! Spawn UI elements with [`node_bundles::ButtonBundle`], [`node_bundles::ImageBundle`], [`node_bundles::TextBundle`] and [`node_bundles::NodeBundle`] diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index f63fd1a0dc36c..97fb405e63659 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -21,6 +21,7 @@ use crate::{ use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, AssetEvent, AssetId, Assets, Handle}; use bevy_ecs::prelude::*; +use bevy_math::Vec3Swizzles; use bevy_math::{Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec4Swizzles}; use bevy_render::{ camera::Camera, @@ -551,6 +552,11 @@ pub fn extract_default_ui_camera_view( if matches!(camera_ui, Some(&UiCameraConfig { show_ui: false, .. })) { continue; } + // ignore inactive cameras + if !camera.is_active { + continue; + } + if let ( Some(logical_size), Some(URect { @@ -618,13 +624,14 @@ pub fn extract_text_uinodes( >, ) { // TODO: Support window-independent UI scale: https://github.com/bevyengine/bevy/issues/5621 - let scale_factor = windows + + let scale_factor = (windows .get_single() - .map(|window| window.resolution.scale_factor()) - .unwrap_or(1.0) - * ui_scale.0; + .map(|window| window.scale_factor()) + .unwrap_or(1.) + * ui_scale.0) as f32; - let inverse_scale_factor = (scale_factor as f32).recip(); + let inverse_scale_factor = scale_factor.recip(); for (uinode, global_transform, text, text_layout_info, view_visibility, clip) in uinode_query.iter() @@ -633,8 +640,20 @@ pub fn extract_text_uinodes( if !view_visibility.get() || uinode.size().x == 0. || uinode.size().y == 0. { continue; } - let transform = global_transform.compute_matrix() - * Mat4::from_translation(-0.5 * uinode.size().extend(0.)); + + let mut affine = global_transform.affine(); + + // Align the text to the nearest physical pixel: + // * Translate by minus the text node's half-size + // (The transform translates to the center of the node but the text coordinates are relative to the node's top left corner) + // * Multiply the logical coordinates by the scale factor to get its position in physical coordinates + // * Round the physical position to the nearest physical pixel + // * Multiply by the rounded physical position by the inverse scale factor to return to logical coordinates + let logical_top_left = affine.translation.xy() - 0.5 * uinode.size(); + let physical_nearest_pixel = (logical_top_left * scale_factor).round(); + let logical_top_left_nearest_pixel = physical_nearest_pixel * inverse_scale_factor; + affine.translation = logical_top_left_nearest_pixel.extend(0.).into(); + let transform = Mat4::from(affine); let mut color = Color::WHITE; let mut current_section = usize::MAX; diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index 11885942822a4..83ef5ef592c77 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -298,10 +298,9 @@ impl RenderCommand

materials: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let material = materials - .into_inner() - .get(&material_handle.material) - .unwrap(); + let Some(material) = materials.into_inner().get(&material_handle.material) else { + return RenderCommandResult::Failure; + }; pass.set_bind_group(I, &material.bind_group, &[]); RenderCommandResult::Success } @@ -732,7 +731,9 @@ pub fn queue_ui_material_nodes( let draw_function = draw_functions.read().id::>(); for (entity, extracted_uinode) in extracted_uinodes.uinodes.iter() { - let material = render_materials.get(&extracted_uinode.material).unwrap(); + let Some(material) = render_materials.get(&extracted_uinode.material) else { + continue; + }; for (view, mut transparent_phase) in &mut views { let pipeline = pipelines.specialize( &pipeline_cache, diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index a02308566b446..2425d7fdb0c74 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1309,6 +1309,7 @@ pub struct GridPlacement { impl GridPlacement { pub const DEFAULT: Self = Self { start: None, + // SAFETY: This is trivially safe as 1 is non-zero. span: Some(unsafe { NonZeroU16::new_unchecked(1) }), end: None, }; diff --git a/crates/bevy_utils/Cargo.toml b/crates/bevy_utils/Cargo.toml index 66778ba3097cb..be3d039da1b10 100644 --- a/crates/bevy_utils/Cargo.toml +++ b/crates/bevy_utils/Cargo.toml @@ -24,3 +24,6 @@ nonmax = "0.5" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2.0", features = ["js"] } + +[lints] +workspace = true diff --git a/crates/bevy_utils/macros/Cargo.toml b/crates/bevy_utils/macros/Cargo.toml index 8a9cb4293f230..fb65c66475555 100644 --- a/crates/bevy_utils/macros/Cargo.toml +++ b/crates/bevy_utils/macros/Cargo.toml @@ -12,3 +12,6 @@ proc-macro = true syn = "2.0" quote = "1.0" proc-macro2 = "1.0" + +[lints] +workspace = true diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 9680d4cf28f81..acfa59323f73d 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -2,9 +2,8 @@ //! //! [Bevy]: https://bevyengine.org/ //! -#![allow(clippy::type_complexity)] + #![warn(missing_docs)] -#![warn(clippy::undocumented_unsafe_blocks)] #[allow(missing_docs)] pub mod prelude { diff --git a/crates/bevy_window/Cargo.toml b/crates/bevy_window/Cargo.toml index 0f2af8438936d..a0561c8e83a1c 100644 --- a/crates/bevy_window/Cargo.toml +++ b/crates/bevy_window/Cargo.toml @@ -19,7 +19,7 @@ bevy_app = { path = "../bevy_app", version = "0.12.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" } bevy_math = { path = "../bevy_math", version = "0.12.0" } bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [ - "glam", + "glam", ] } bevy_utils = { path = "../bevy_utils", version = "0.12.0" } # Used for close_on_esc @@ -28,3 +28,6 @@ raw-window-handle = "0.5" # other serde = { version = "1.0", features = ["derive"], optional = true } + +[lints] +workspace = true diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index f1e8a304c52a5..3846814097388 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -1,4 +1,3 @@ -#![allow(clippy::type_complexity)] #![warn(missing_docs)] //! `bevy_window` provides a platform-agnostic interface for windowing in Bevy. //! diff --git a/crates/bevy_window/src/raw_handle.rs b/crates/bevy_window/src/raw_handle.rs index 06b36ee9ceb6a..580de90b293b7 100644 --- a/crates/bevy_window/src/raw_handle.rs +++ b/crates/bevy_window/src/raw_handle.rs @@ -34,6 +34,7 @@ impl RawHandleWrapper { // A recommendation for this pattern (and more context) is available here: // https://github.com/rust-windowing/raw-window-handle/issues/59 unsafe impl Send for RawHandleWrapper {} +// SAFETY: This is safe for the same reasons as the Send impl above. unsafe impl Sync for RawHandleWrapper {} /// A [`RawHandleWrapper`] that cannot be sent across threads. diff --git a/crates/bevy_winit/Cargo.toml b/crates/bevy_winit/Cargo.toml index d4651eff7c1e6..5b81b64585628 100644 --- a/crates/bevy_winit/Cargo.toml +++ b/crates/bevy_winit/Cargo.toml @@ -35,8 +35,8 @@ raw-window-handle = "0.5" [target.'cfg(target_os = "android")'.dependencies] winit = { version = "0.29", default-features = false, features = [ - "android-native-activity", - "rwh_05", + "android-native-activity", + "rwh_05", ] } [target.'cfg(target_arch = "wasm32")'.dependencies] @@ -47,3 +47,6 @@ crossbeam-channel = "0.5" [package.metadata.docs.rs] features = ["x11"] + +[lints] +workspace = true diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 3bf79529a464b..fba7bf66676f9 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -1,4 +1,3 @@ -#![allow(clippy::type_complexity)] #![warn(missing_docs)] //! `bevy_winit` provides utilities to handle window creation and the eventloop through [`winit`] //! @@ -312,7 +311,15 @@ impl Default for WinitAppRunnerState { /// Overriding the app's [runner](bevy_app::App::runner) while using `WinitPlugin` will bypass the /// `EventLoop`. pub fn winit_runner(mut app: App) { - let event_loop = app + if app.plugins_state() == PluginsState::Ready { + // If we're already ready, we finish up now and advance one frame. + // This prevents black frames during the launch transition on iOS. + app.finish(); + app.cleanup(); + app.update(); + } + + let mut event_loop = app .world .remove_non_send_resource::>() .unwrap(); @@ -654,16 +661,22 @@ pub fn winit_runner(mut app: App) { cursor, }); } - event::Ime::Commit(value) => event_writers.ime_input.send(Ime::Commit { - window: window_entity, - value, - }), - event::Ime::Enabled => event_writers.ime_input.send(Ime::Enabled { - window: window_entity, - }), - event::Ime::Disabled => event_writers.ime_input.send(Ime::Disabled { - window: window_entity, - }), + event::Ime::Commit(value) => { + event_writers.ime_input.send(Ime::Commit { + window: window_entity, + value, + }); + } + event::Ime::Enabled => { + event_writers.ime_input.send(Ime::Enabled { + window: window_entity, + }); + } + event::Ime::Disabled => { + event_writers.ime_input.send(Ime::Disabled { + window: window_entity, + }); + } }, WindowEvent::ThemeChanged(theme) => { event_writers.window_theme_changed.send(WindowThemeChanged { diff --git a/deny.toml b/deny.toml index 83b226f459c67..5d68e5ea5e5d7 100644 --- a/deny.toml +++ b/deny.toml @@ -1,3 +1,5 @@ +all-features = true + [advisories] db-path = "~/.cargo/advisory-db" db-urls = ["https://github.com/rustsec/advisory-db"] @@ -5,37 +7,74 @@ vulnerability = "deny" unmaintained = "deny" yanked = "deny" notice = "deny" -ignore = [ -] +ignore = [] [licenses] unlicensed = "deny" copyleft = "deny" +default = "deny" allow = [ - "MIT", - "MIT-0", - "Apache-2.0", - "BSD-3-Clause", - "ISC", - "Zlib", - "0BSD", - "BSD-2-Clause", - "CC0-1.0", + "MIT", + "MIT-0", + "Apache-2.0", + "BSD-3-Clause", + "ISC", + "Zlib", + "0BSD", + "BSD-2-Clause", + "CC0-1.0", ] exceptions = [ - { name = "unicode-ident", allow = ["Unicode-DFS-2016"] }, + { name = "unicode-ident", allow = [ + "Unicode-DFS-2016", + ] }, + { name = "symphonia-bundle-flac", allow = [ + "MPL-2.0", + ] }, + { name = "symphonia-bundle-mp3", allow = [ + "MPL-2.0", + ] }, + { name = "symphonia-codec-aac", allow = [ + "MPL-2.0", + ] }, + { name = "symphonia-codec-adpcm", allow = [ + "MPL-2.0", + ] }, + { name = "symphonia-codec-pcm", allow = [ + "MPL-2.0", + ] }, + { name = "symphonia-codec-vorbis", allow = [ + "MPL-2.0", + ] }, + { name = "symphonia-core", allow = [ + "MPL-2.0", + ] }, + { name = "symphonia-format-isomp4", allow = [ + "MPL-2.0", + ] }, + { name = "symphonia-format-wav", allow = [ + "MPL-2.0", + ] }, + { name = "symphonia-metadata", allow = [ + "MPL-2.0", + ] }, + { name = "symphonia-utils-xiph", allow = [ + "MPL-2.0", + ] }, + { name = "symphonia", allow = [ + "MPL-2.0", + ] }, ] -default = "deny" [bans] multiple-versions = "warn" wildcards = "deny" # Certain crates that we don't want multiple versions of in the dependency tree deny = [ - { name = "ahash", deny-multiple-versions = true }, - { name = "android-activity", deny-multiple-versions = true }, - { name = "glam", deny-multiple-versions = true }, - { name = "raw-window-handle", deny-multiple-versions = true }, + { name = "ahash", deny-multiple-versions = true }, + { name = "android-activity", deny-multiple-versions = true }, + { name = "glam", deny-multiple-versions = true }, + { name = "raw-window-handle", deny-multiple-versions = true }, ] [sources] diff --git a/docs/profiling.md b/docs/profiling.md index fedd62ff64aba..7f0dc155cbfbc 100644 --- a/docs/profiling.md +++ b/docs/profiling.md @@ -40,7 +40,7 @@ The [Tracy profiling tool](https://github.com/wolfpld/tracy) is: There are binaries available for Windows, and installation / build instructions for other operating systems can be found in the [Tracy documentation PDF](https://github.com/wolfpld/tracy/releases/latest/download/tracy.pdf). -It has a command line capture tool that can record the execution of graphical applications, saving it as a profile file. Tracy has a GUI to inspect these profile files. The GUI app also supports live capture, showing you in real time the trace of your app. The version of tracy must be matched to the version of tracing-tracy used in bevy. A compatibility table can be found on [crates.io](https://crates.io/crates/tracing-tracy) and the version used can be found [here](https://github.com/bevyengine/bevy/blob/latest/crates/bevy_log/Cargo.toml#L21). +It has a command line capture tool that can record the execution of graphical applications, saving it as a profile file. Tracy has a GUI to inspect these profile files. The GUI app also supports live capture, showing you in real time the trace of your app. The version of tracy must be matched to the version of tracing-tracy used in bevy. A compatibility table can be found on [crates.io](https://crates.io/crates/tracing-tracy) and the version used can be found [here](https://github.com/bevyengine/bevy/blob/latest/crates/bevy_log/Cargo.toml#L25). In one terminal, run: `./capture-release -o my_capture.tracy` diff --git a/examples/2d/2d_gizmos.rs b/examples/2d/2d_gizmos.rs index a70cd250a2976..740f83803e989 100644 --- a/examples/2d/2d_gizmos.rs +++ b/examples/2d/2d_gizmos.rs @@ -53,6 +53,12 @@ fn system(mut gizmos: Gizmos, time: Res)>, +) { + for (entity, Compressed { compressed, .. }) in query.iter() { + let Some(GzAsset { uncompressed }) = compressed_assets.remove(compressed) else { + continue; + }; + + let uncompressed = uncompressed.take::().unwrap(); + + commands + .entity(entity) + .remove::>() + .insert(asset_server.add(uncompressed)); + } +} diff --git a/examples/asset/custom_asset.rs b/examples/asset/custom_asset.rs index c0aa0ad9b83b9..07450ef4744f1 100644 --- a/examples/asset/custom_asset.rs +++ b/examples/asset/custom_asset.rs @@ -2,12 +2,11 @@ use bevy::utils::thiserror; use bevy::{ - asset::{io::Reader, AssetLoader, LoadContext}, + asset::{io::Reader, ron, AssetLoader, AsyncReadExt, LoadContext}, prelude::*, reflect::TypePath, utils::BoxedFuture, }; -use futures_lite::AsyncReadExt; use serde::Deserialize; use thiserror::Error; @@ -24,7 +23,7 @@ pub struct CustomAssetLoader; #[derive(Debug, Error)] pub enum CustomAssetLoaderError { /// An [IO](std::io) Error - #[error("Could load shader: {0}")] + #[error("Could not load asset: {0}")] Io(#[from] std::io::Error), /// A [RON](ron) Error #[error("Could not parse RON: {0}")] diff --git a/examples/asset/custom_asset_reader.rs b/examples/asset/custom_asset_reader.rs index 3064cd12593c6..4e4b0eede68db 100644 --- a/examples/asset/custom_asset_reader.rs +++ b/examples/asset/custom_asset_reader.rs @@ -3,19 +3,16 @@ //! It does not know anything about the asset formats, only how to talk to the underlying storage. use bevy::{ - asset::io::{ - file::FileAssetReader, AssetReader, AssetReaderError, AssetSource, AssetSourceId, - PathStream, Reader, - }, + asset::io::{AssetReader, AssetReaderError, AssetSource, AssetSourceId, PathStream, Reader}, prelude::*, utils::BoxedFuture, }; use std::path::Path; /// A custom asset reader implementation that wraps a given asset reader implementation -struct CustomAssetReader(T); +struct CustomAssetReader(Box); -impl AssetReader for CustomAssetReader { +impl AssetReader for CustomAssetReader { fn read<'a>( &'a self, path: &'a Path, @@ -52,8 +49,12 @@ impl Plugin for CustomAssetReaderPlugin { fn build(&self, app: &mut App) { app.register_asset_source( AssetSourceId::Default, - AssetSource::build() - .with_reader(|| Box::new(CustomAssetReader(FileAssetReader::new("assets")))), + AssetSource::build().with_reader(|| { + Box::new(CustomAssetReader( + // This is the default reader for the current platform + AssetSource::get_default_reader("assets".to_string())(), + )) + }), ); } } diff --git a/examples/asset/processing/asset_processing.rs b/examples/asset/processing/asset_processing.rs index 92c2884b627ab..5947be2ab482a 100644 --- a/examples/asset/processing/asset_processing.rs +++ b/examples/asset/processing/asset_processing.rs @@ -5,6 +5,7 @@ use bevy::{ embedded_asset, io::{Reader, Writer}, processor::LoadAndSave, + ron, saver::{AssetSaver, SavedAsset}, AssetLoader, AsyncReadExt, AsyncWriteExt, LoadContext, }, diff --git a/examples/async_tasks/async_compute.rs b/examples/async_tasks/async_compute.rs index 9fad4d30a9c2e..ebf4eaa986b55 100644 --- a/examples/async_tasks/async_compute.rs +++ b/examples/async_tasks/async_compute.rs @@ -2,10 +2,10 @@ //! to spawn, poll, and complete tasks across systems and system ticks. use bevy::{ + ecs::system::{CommandQueue, SystemState}, prelude::*, - tasks::{block_on, AsyncComputeTaskPool, Task}, + tasks::{block_on, futures_lite::future, AsyncComputeTaskPool, Task}, }; -use futures_lite::future; use rand::Rng; use std::time::{Duration, Instant}; @@ -43,7 +43,7 @@ fn add_assets( } #[derive(Component)] -struct ComputeTransform(Task); +struct ComputeTransform(Task); /// This system generates tasks simulating computationally intensive /// work that potentially spans multiple frames/ticks. A separate @@ -57,6 +57,7 @@ fn spawn_tasks(mut commands: Commands) { // Spawn new task on the AsyncComputeTaskPool; the task will be // executed in the background, and the Task future returned by // spawn() can be used to poll for the result + let entity = commands.spawn_empty().id(); let task = thread_pool.spawn(async move { let mut rng = rand::thread_rng(); let start_time = Instant::now(); @@ -67,11 +68,41 @@ fn spawn_tasks(mut commands: Commands) { } // Such hard work, all done! - Transform::from_xyz(x as f32, y as f32, z as f32) + let transform = Transform::from_xyz(x as f32, y as f32, z as f32); + let mut command_queue = CommandQueue::default(); + + // we use a raw command queue to pass a FnOne(&mut World) back to be + // applied in a deferred manner. + command_queue.push(move |world: &mut World| { + let (box_mesh_handle, box_material_handle) = { + let mut system_state = SystemState::<( + Res, + Res, + )>::new(world); + let (box_mesh_handle, box_material_handle) = + system_state.get_mut(world); + + (box_mesh_handle.clone(), box_material_handle.clone()) + }; + + world + .entity_mut(entity) + // Add our new PbrBundle of components to our tagged entity + .insert(PbrBundle { + mesh: box_mesh_handle, + material: box_material_handle, + transform, + ..default() + }) + // Task is complete, so remove task component from entity + .remove::(); + }); + + command_queue }); // Spawn new entity and add our new task as a component - commands.spawn(ComputeTransform(task)); + commands.entity(entity).insert(ComputeTransform(task)); } } } @@ -81,24 +112,11 @@ fn spawn_tasks(mut commands: Commands) { /// tasks to see if they're complete. If the task is complete it takes the result, adds a /// new [`PbrBundle`] of components to the entity using the result from the task's work, and /// removes the task component from the entity. -fn handle_tasks( - mut commands: Commands, - mut transform_tasks: Query<(Entity, &mut ComputeTransform)>, - box_mesh_handle: Res, - box_material_handle: Res, -) { - for (entity, mut task) in &mut transform_tasks { - if let Some(transform) = block_on(future::poll_once(&mut task.0)) { - // Add our new PbrBundle of components to our tagged entity - commands.entity(entity).insert(PbrBundle { - mesh: box_mesh_handle.clone(), - material: box_material_handle.clone(), - transform, - ..default() - }); - - // Task is complete, so remove task component from entity - commands.entity(entity).remove::(); +fn handle_tasks(mut commands: Commands, mut transform_tasks: Query<&mut ComputeTransform>) { + for mut task in &mut transform_tasks { + if let Some(mut commands_queue) = block_on(future::poll_once(&mut task.0)) { + // append the returned command queue to have it execute later + commands.append(&mut commands_queue); } } } diff --git a/examples/ecs/custom_query_param.rs b/examples/ecs/custom_query_param.rs index 81a6d8cfa718a..8cc5f172073b9 100644 --- a/examples/ecs/custom_query_param.rs +++ b/examples/ecs/custom_query_param.rs @@ -12,10 +12,6 @@ //! //! For more details on the `WorldQuery` derive macro, see the trait documentation. -// This lint usually gives bad advice in the context of Bevy -- hiding complex queries behind -// type aliases tends to obfuscate code while offering no improvement in code cleanliness. -#![allow(clippy::type_complexity)] - use bevy::{ecs::query::WorldQuery, prelude::*}; use std::fmt::Debug; diff --git a/examples/ecs/state.rs b/examples/ecs/state.rs index 0d55e4c290880..54865098f43ff 100644 --- a/examples/ecs/state.rs +++ b/examples/ecs/state.rs @@ -5,10 +5,6 @@ //! //! In this case, we're transitioning from a `Menu` state to an `InGame` state. -// This lint usually gives bad advice in the context of Bevy -- hiding complex queries behind -// type aliases tends to obfuscate code while offering no improvement in code cleanliness. -#![allow(clippy::type_complexity)] - use bevy::prelude::*; fn main() { diff --git a/examples/games/alien_cake_addict.rs b/examples/games/alien_cake_addict.rs index 9ba902a8abde3..b68008cfaeaa3 100644 --- a/examples/games/alien_cake_addict.rs +++ b/examples/games/alien_cake_addict.rs @@ -1,9 +1,5 @@ //! Eat the cakes. Eat them all. An example 3D game. -// This lint usually gives bad advice in the context of Bevy -- hiding complex queries behind -// type aliases tends to obfuscate code while offering no improvement in code cleanliness. -#![allow(clippy::type_complexity)] - use std::f32::consts::PI; use bevy::prelude::*; diff --git a/examples/games/game_menu.rs b/examples/games/game_menu.rs index 4beaa03c603f1..5b4b5a2457c81 100644 --- a/examples/games/game_menu.rs +++ b/examples/games/game_menu.rs @@ -2,10 +2,6 @@ //! change some settings or quit. There is no actual game, it will just display the current //! settings for 5 seconds before going back to the menu. -// This lint usually gives bad advice in the context of Bevy -- hiding complex queries behind -// type aliases tends to obfuscate code while offering no improvement in code cleanliness. -#![allow(clippy::type_complexity)] - use bevy::prelude::*; const TEXT_COLOR: Color = Color::rgb(0.9, 0.9, 0.9); @@ -804,7 +800,9 @@ mod menu { for (interaction, menu_button_action) in &interaction_query { if *interaction == Interaction::Pressed { match menu_button_action { - MenuButtonAction::Quit => app_exit_events.send(AppExit), + MenuButtonAction::Quit => { + app_exit_events.send(AppExit); + } MenuButtonAction::Play => { game_state.set(GameState::Game); menu_state.set(MenuState::Disabled); diff --git a/examples/input/text_input.rs b/examples/input/text_input.rs index 0959b1384dc4d..a372fda100b7f 100644 --- a/examples/input/text_input.rs +++ b/examples/input/text_input.rs @@ -4,10 +4,6 @@ //! Clicking toggle IME (Input Method Editor) support, but the font used as limited support of characters. //! You should change the provided font with another one to test other languages input. -// This lint usually gives bad advice in the context of Bevy -- hiding complex queries behind -// type aliases tends to obfuscate code while offering no improvement in code cleanliness. -#![allow(clippy::type_complexity)] - use bevy::{input::keyboard::KeyboardInput, prelude::*}; fn main() { diff --git a/examples/mobile/Cargo.toml b/examples/mobile/Cargo.toml index 02c7d3f8a3363..194970d0728c9 100644 --- a/examples/mobile/Cargo.toml +++ b/examples/mobile/Cargo.toml @@ -29,3 +29,5 @@ target_sdk_version = 31 icon = "@mipmap/ic_launcher" label = "Bevy Example" +[lints] +workspace = true diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index a4715c43689b0..7d36803928b33 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -1,7 +1,3 @@ -// This lint usually gives bad advice in the context of Bevy -- hiding complex queries behind -// type aliases tends to obfuscate code while offering no improvement in code cleanliness. -#![allow(clippy::type_complexity)] - use bevy::{ input::touch::TouchPhase, prelude::*, diff --git a/examples/shader/extended_material.rs b/examples/shader/extended_material.rs index 704537423708a..7f055a9bef860 100644 --- a/examples/shader/extended_material.rs +++ b/examples/shader/extended_material.rs @@ -1,6 +1,5 @@ //! Demonstrates using a custom extension to the `StandardMaterial` to modify the results of the builtin pbr shader. -use bevy::reflect::TypePath; use bevy::{ pbr::{ExtendedMaterial, MaterialExtension, OpaqueRendererMethod}, prelude::*, @@ -73,7 +72,7 @@ fn rotate_things(mut q: Query<&mut Transform, With>, time: Res